Accesso ai dati utilizzando i DAO delle stanze

Quando utilizzi la libreria di persistenza della stanza virtuale per archiviare i dati dell'app, interagisci con i dati archiviati definendo oggetti di accesso ai dati, o DAO. Ogni DAO include metodi che offrono accesso astratto al database dell'app. In fase di compilazione, Room genera automaticamente le implementazioni dei DAO che definisci.

Utilizzando i DAO per accedere al database dell'app anziché agli strumenti per la creazione di query o alle query dirette, puoi preservare la separazione delle preoccupazioni, un principio dell'architettura fondamentale. I DAO semplificano inoltre la simulazione dell'accesso al database quando testi l'app.

Anatomia di un DAO

Puoi definire ciascun DAO come un'interfaccia o come una classe astratta. Per i casi d'uso di base, in genere viene utilizzata un'interfaccia. In entrambi i casi, devi sempre annotare i tuoi DAO con @Dao. I DAO non hanno proprietà, ma definiscono uno o più metodi per interagire con i dati nel database dell'app.

Il codice seguente è un esempio di DAO semplice che definisce i metodi per inserire, eliminare e selezionare oggetti User in un database di stanze:

Kotlin

@Dao
interface UserDao {
    @Insert
    fun insertAll(vararg users: User)

    @Delete
    fun delete(user: User)

    @Query("SELECT * FROM user")
    fun getAll(): List<User>
}

Java

@Dao
public interface UserDao {
    @Insert
    void insertAll(User... users);

    @Delete
    void delete(User user);

    @Query("SELECT * FROM user")
    List<User> getAll();
}

Esistono due tipi di metodi DAO che definiscono le interazioni con i database:

  • Metodi pratici che ti consentono di inserire, aggiornare ed eliminare righe nel database senza scrivere codice SQL.
  • Metodi di query che consentono di scrivere query SQL personalizzate per interagire con il database.

Le seguenti sezioni mostrano come utilizzare entrambi i tipi di metodi DAO per definire le interazioni con il database necessarie per la tua app.

Metodi pratici

Room fornisce annotazioni di servizio per definire metodi che eseguono semplici inserzioni, aggiornamenti ed eliminazioni senza la necessità di scrivere un'istruzione SQL.

Se devi definire posizionamenti, aggiornamenti o eliminazioni più complessi oppure se devi eseguire query sui dati nel database, utilizza un metodo di query.

Inserisci

L'annotazione @Insert consente di definire metodi che inseriscono i parametri nella tabella appropriata del database. Il codice seguente mostra esempi di metodi @Insert validi che inseriscono uno o più oggetti User nel database:

Kotlin

@Dao
interface UserDao {
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun insertUsers(vararg users: User)

    @Insert
    fun insertBothUsers(user1: User, user2: User)

    @Insert
    fun insertUsersAndFriends(user: User, friends: List<User>)
}

Java

@Dao
public interface UserDao {
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    public void insertUsers(User... users);

    @Insert
    public void insertBothUsers(User user1, User user2);

    @Insert
    public void insertUsersAndFriends(User user, List<User> friends);
}

Ogni parametro di un metodo @Insert deve essere un'istanza di una classe Entità dei dati sulle camere annotata con @Entity o una raccolta di istanze della classe dell'entità di dati, ciascuna delle quali rimanda a un database. Quando viene richiamato un metodo @Insert, la stanza virtuale inserisce ogni istanza dell'entità superata nella tabella di database corrispondente.

Se il metodo @Insert riceve un singolo parametro, può restituire un valore long, che corrisponde al nuovo rowId per l'elemento inserito. Se il parametro è un array o una raccolta, restituisce invece un array o una raccolta di valori long, con ogni valore come rowId per uno degli elementi inseriti. Per scoprire di più sulla restituzione dei valori rowId, consulta la documentazione di riferimento per l'annotazione @Insert e la documentazione SQLite per le tabelle rowid.

Aggiorna

L'annotazione @Update consente di definire metodi che aggiornano righe specifiche in una tabella di database. Come i metodi @Insert, anche quelli @Update accettano come parametri le istanze dell'entità dati. Il codice seguente mostra un esempio di metodo @Update che tenta di aggiornare uno o più oggetti User nel database:

Kotlin

@Dao
interface UserDao {
    @Update
    fun updateUsers(vararg users: User)
}

Java

@Dao
public interface UserDao {
    @Update
    public void updateUsers(User... users);
}

La stanza virtuale utilizza la chiave primaria per abbinare le istanze dell'entità passate alle righe del database. Se non è presente una riga con la stessa chiave principale, la stanza virtuale non apporta modifiche.

Un metodo @Update può restituire facoltativamente un valore int che indica il numero di righe aggiornate correttamente.

Elimina

L'annotazione @Delete consente di definire metodi che eliminano righe specifiche da una tabella di database. Come i metodi @Insert, anche quelli @Delete accettano come parametri le istanze dell'entità dati. Il codice seguente mostra un esempio di metodo @Delete che tenta di eliminare uno o più oggetti User dal database:

Kotlin

@Dao
interface UserDao {
    @Delete
    fun deleteUsers(vararg users: User)
}

Java

@Dao
public interface UserDao {
    @Delete
    public void deleteUsers(User... users);
}

La stanza virtuale utilizza la chiave primaria per abbinare le istanze dell'entità passate alle righe del database. Se non è presente una riga con la stessa chiave principale, la stanza virtuale non apporta modifiche.

Un metodo @Delete può restituire facoltativamente un valore int che indica il numero di righe eliminate correttamente.

Metodi di query

L'annotazione @Query consente di scrivere istruzioni SQL ed esporle come metodi DAO. Utilizza questi metodi di query per eseguire query sui dati del database della tua app o quando devi eseguire inserti, aggiornamenti ed eliminazioni più complessi.

La stanza virtuale convalida le query SQL in fase di compilazione. Ciò significa che se si verifica un problema con la query, si verifica un errore di compilazione anziché un errore di runtime.

Query semplici

Il codice seguente definisce un metodo che utilizza una semplice query SELECT per restituire tutti gli oggetti User del database:

Kotlin

@Query("SELECT * FROM user")
fun loadAllUsers(): Array<User>

Java

@Query("SELECT * FROM user")
public User[] loadAllUsers();

Le seguenti sezioni mostrano come modificare questo esempio per i casi d'uso tipici.

Restituire un sottoinsieme delle colonne di una tabella

Nella maggior parte dei casi, è sufficiente restituire un sottoinsieme di colonne dalla tabella su cui stai eseguendo la query. Ad esempio, l'interfaccia utente potrebbe mostrare solo il nome e il cognome di un utente, anziché ogni dettaglio sull'utente. Per salvare le risorse e semplificare l'esecuzione della query, esegui la query solo sui campi che ti servono.

La stanza virtuale ti consente di restituire un oggetto semplice da una qualsiasi delle tue query purché tu possa mappare l'insieme di colonne dei risultati sull'oggetto restituito. Ad esempio, puoi definire il seguente oggetto in cui inserire il nome e il cognome di un utente:

Kotlin

data class NameTuple(
    @ColumnInfo(name = "first_name") val firstName: String?,
    @ColumnInfo(name = "last_name") val lastName: String?
)

Java

public class NameTuple {
    @ColumnInfo(name = "first_name")
    public String firstName;

    @ColumnInfo(name = "last_name")
    @NonNull
    public String lastName;
}

Quindi, puoi restituire questo semplice oggetto dal tuo metodo di query:

Kotlin

@Query("SELECT first_name, last_name FROM user")
fun loadFullName(): List<NameTuple>

Java

@Query("SELECT first_name, last_name FROM user")
public List<NameTuple> loadFullName();

La stanza virtuale riconosce che la query restituisce i valori per le colonne first_name e last_name e che questi valori possono essere mappati ai campi della classe NameTuple. Se la query restituisce una colonna non mappata su un campo nell'oggetto restituito, la stanza virtuale mostra un avviso.

Trasferire parametri semplici a una query

La maggior parte delle volte, i metodi DAO devono accettare parametri in modo da poter eseguire operazioni di filtro. La camera supporta l'utilizzo di parametri di metodo come parametri di associazione nelle query.

Ad esempio, il codice seguente definisce un metodo che restituisce tutti gli utenti al di sopra di una determinata età:

Kotlin

@Query("SELECT * FROM user WHERE age > :minAge")
fun loadAllUsersOlderThan(minAge: Int): Array<User>

Java

@Query("SELECT * FROM user WHERE age > :minAge")
public User[] loadAllUsersOlderThan(int minAge);

Puoi anche passare più parametri o fare riferimento allo stesso parametro più volte in una query, come mostrato nel codice seguente:

Kotlin

@Query("SELECT * FROM user WHERE age BETWEEN :minAge AND :maxAge")
fun loadAllUsersBetweenAges(minAge: Int, maxAge: Int): Array<User>

@Query("SELECT * FROM user WHERE first_name LIKE :search " +
       "OR last_name LIKE :search")
fun findUserWithName(search: String): List<User>

Java

@Query("SELECT * FROM user WHERE age BETWEEN :minAge AND :maxAge")
public User[] loadAllUsersBetweenAges(int minAge, int maxAge);

@Query("SELECT * FROM user WHERE first_name LIKE :search " +
       "OR last_name LIKE :search")
public List<User> findUserWithName(String search);

Passare una raccolta di parametri a una query

Alcuni dei tuoi metodi DAO potrebbero richiedere il trasferimento di un numero variabile di parametri non noti fino al runtime. La stanza virtuale capisce quando un parametro rappresenta una raccolta e la espande automaticamente in fase di runtime in base al numero di parametri forniti.

Ad esempio, il codice seguente definisce un metodo che restituisce informazioni su tutti gli utenti di un sottoinsieme di regioni:

Kotlin

@Query("SELECT * FROM user WHERE region IN (:regions)")
fun loadUsersFromRegions(regions: List<String>): List<User>

Java

@Query("SELECT * FROM user WHERE region IN (:regions)")
public List<User> loadUsersFromRegions(List<String> regions);

Eseguire query su più tabelle

Alcune delle tue query potrebbero richiedere l'accesso a più tabelle per calcolare il risultato. Puoi utilizzare le clausole JOIN nelle query SQL per fare riferimento a più tabelle.

Il codice seguente definisce un metodo che unisce tre tabelle per restituire i libri attualmente in prestito a un utente specifico:

Kotlin

@Query(
    "SELECT * FROM book " +
    "INNER JOIN loan ON loan.book_id = book.id " +
    "INNER JOIN user ON user.id = loan.user_id " +
    "WHERE user.name LIKE :userName"
)
fun findBooksBorrowedByNameSync(userName: String): List<Book>

Java

@Query("SELECT * FROM book " +
       "INNER JOIN loan ON loan.book_id = book.id " +
       "INNER JOIN user ON user.id = loan.user_id " +
       "WHERE user.name LIKE :userName")
public List<Book> findBooksBorrowedByNameSync(String userName);

Puoi inoltre definire oggetti semplici in modo che restituiscano un sottoinsieme di colonne di più tabelle unite, come descritto nella sezione Restituisci un sottoinsieme di colonne di una tabella. Il codice seguente definisce un DAO con un metodo che restituisce i nomi degli utenti e dei libri presi in prestito:

Kotlin

interface UserBookDao {
    @Query(
        "SELECT user.name AS userName, book.name AS bookName " +
        "FROM user, book " +
        "WHERE user.id = book.user_id"
    )
    fun loadUserAndBookNames(): LiveData<List<UserBook>>

    // You can also define this class in a separate file.
    data class UserBook(val userName: String?, val bookName: String?)
}

Java

@Dao
public interface UserBookDao {
   @Query("SELECT user.name AS userName, book.name AS bookName " +
          "FROM user, book " +
          "WHERE user.id = book.user_id")
   public LiveData<List<UserBook>> loadUserAndBookNames();

   // You can also define this class in a separate file, as long as you add the
   // "public" access modifier.
   static class UserBook {
       public String userName;
       public String bookName;
   }
}

Restituire una mappa multipla

Nella stanza 2.4 e nelle versioni successive, puoi anche eseguire query su colonne di più tabelle senza definire una classe di dati aggiuntiva scrivendo metodi di query che restituiscano una multimappa.

Considera l'esempio della sezione Esegui query su più tabelle. Anziché restituire un elenco di istanze di una classe di dati personalizzata che contiene le coppie di istanze User e Book, puoi restituire una mappatura di User e Book direttamente dal tuo metodo di query:

Kotlin

@Query(
    "SELECT * FROM user" +
    "JOIN book ON user.id = book.user_id"
)
fun loadUserAndBookNames(): Map<User, List<Book>>

Java

@Query(
    "SELECT * FROM user" +
    "JOIN book ON user.id = book.user_id"
)
public Map<User, List<Book>> loadUserAndBookNames();

Quando il tuo metodo di query restituisce una mappa multimappa, puoi scrivere query che utilizzano clausole GROUP BY, in modo da sfruttare le funzionalità di SQL per calcoli e filtri avanzati. Ad esempio, puoi modificare il tuo metodo loadUserAndBookNames() per restituire solo gli utenti con tre o più libri pagati:

Kotlin

@Query(
    "SELECT * FROM user" +
    "JOIN book ON user.id = book.user_id" +
    "GROUP BY user.name WHERE COUNT(book.id) >= 3"
)
fun loadUserAndBookNames(): Map<User, List<Book>>

Java

@Query(
    "SELECT * FROM user" +
    "JOIN book ON user.id = book.user_id" +
    "GROUP BY user.name WHERE COUNT(book.id) >= 3"
)
public Map<User, List<Book>> loadUserAndBookNames();

Se non devi mappare interi oggetti, puoi anche restituire mappature tra colonne specifiche nella tua query impostando gli attributi keyColumn e valueColumn in un'annotazione @MapInfo nel metodo di query:

Kotlin

@MapInfo(keyColumn = "userName", valueColumn = "bookName")
@Query(
    "SELECT user.name AS username, book.name AS bookname FROM user" +
    "JOIN book ON user.id = book.user_id"
)
fun loadUserAndBookNames(): Map<String, List<String>>

Java

@MapInfo(keyColumn = "userName", valueColumn = "bookName")
@Query(
    "SELECT user.name AS username, book.name AS bookname FROM user" +
    "JOIN book ON user.id = book.user_id"
)
public Map<String, List<String>> loadUserAndBookNames();

Tipi di reso speciali

Room fornisce alcuni tipi speciali di reso per l'integrazione con altre librerie API.

Query suddivise in pagine con la libreria Paging

La stanza virtuale supporta le query impaginate tramite l'integrazione con la libreria di pagine di destinazione. Nella stanza 2.3.0-alpha01 e versioni successive, gli oggetti DAO possono restituire oggetti PagingSource da utilizzare con la Paging 3.

Kotlin

@Dao
interface UserDao {
  @Query("SELECT * FROM users WHERE label LIKE :query")
  fun pagingSource(query: String): PagingSource<Int, User>
}

Java

@Dao
interface UserDao {
  @Query("SELECT * FROM users WHERE label LIKE :query")
  PagingSource<Integer, User> pagingSource(String query);
}

Per saperne di più sulla scelta dei parametri del tipo per un elemento PagingSource, consulta Selezionare tipi di chiave e valore.

Accesso diretto al cursore

Se la logica della tua app richiede l'accesso diretto alle righe di ritorno, puoi scrivere i tuoi metodi DAO per restituire un oggetto Cursor, come mostrato nell'esempio seguente:

Kotlin

@Dao
interface UserDao {
    @Query("SELECT * FROM user WHERE age > :minAge LIMIT 5")
    fun loadRawUsersOlderThan(minAge: Int): Cursor
}

Java

@Dao
public interface UserDao {
    @Query("SELECT * FROM user WHERE age > :minAge LIMIT 5")
    public Cursor loadRawUsersOlderThan(int minAge);
}

Risorse aggiuntive

Per scoprire di più sull'accesso ai dati utilizzando i DAO delle stanze, consulta le seguenti risorse aggiuntive:

Samples

Codelab