Lorsque vous utilisez la bibliothèque de persistance Room pour stocker les données de votre application, vous interagissez avec les données stockées en définissant des objets d'accès aux données, ou DAO. Chaque DAO inclut des méthodes offrant un accès abstrait à la base de données de votre application. Au moment de la compilation, Room génère automatiquement des implémentations des DAO que vous définissez.
En utilisant des DAO pour accéder à la base de données de votre application au lieu de créer des compilateurs ou des requêtes directes, vous préservez la séparation des préoccupations, qui est un principe architectural essentiel. Les DAO vous permettent également de simuler plus facilement l'accès à la base de données lorsque vous testez votre application.
Anatomie d'un DAO
Vous pouvez définir chaque DAO en tant qu'interface ou classe abstraite. Pour les cas d'utilisation de base, vous devez généralement recourir à une interface. Dans les deux cas, vous devez toujours annoter vos DAO avec @Dao
. Les DAO ne contiennent pas de propriétés, mais ils définissent une ou plusieurs méthodes permettant d'interagir avec les données de la base de données de votre application.
Le code suivant est un exemple de DAO simple qui définit les méthodes d'insertion, de suppression et de sélection d'objets User
dans une base de données Room :
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(); }
Il existe deux types de méthodes DAO qui définissent les interactions avec la base de données :
- Les méthodes de base vous permettent d'insérer, de mettre à jour et de supprimer des lignes dans votre base de données sans avoir à écrire de code SQL.
- Les méthodes de requête vous permettent d'écrire votre propre requête SQL pour interagir avec la base de données.
Les sections suivantes expliquent comment utiliser les deux types de méthodes DAO pour définir les interactions de base de données dont votre application a besoin.
Méthodes de base
Room fournit des annotations de base permettant de définir des méthodes qui effectuent des insertions, des mises à jour et des suppressions simples sans que vous ayez besoin d'écrire une instruction SQL.
Si vous devez définir des insertions, des mises à jour ou des suppressions plus complexes, ou si vous devez interroger les données dans la base de données, utilisez plutôt une méthode de requête.
Annotation "Insert"
L'annotation @Insert
vous permet de définir des méthodes qui insèrent leurs paramètres dans la table appropriée de la base de données. Le code suivant présente des exemples de méthodes @Insert
valides qui insèrent un ou plusieurs objets User
dans la base de données :
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); }
Chaque paramètre d'une méthode @Insert
doit être une instance d'une classe d'entités Room annotée avec @Entity
ou une collection d'instances de classe d'entité de données, chacune pointant vers une base de données. Lorsqu'une méthode @Insert
est appelée, Room insère chaque instance d'entité transmise dans la table de base de données correspondante.
Si la méthode @Insert
ne reçoit qu'un seul paramètre, elle peut renvoyer une valeur long
, qui correspond au nouvel ID de ligne (rowId
) de l'élément inséré. Si le paramètre est un tableau ou une collection, renvoyez un tableau ou une collection de valeurs long
à la place. Chaque valeur doit correspondre au rowId
de l'un des éléments insérés. Pour en savoir plus sur le renvoi des valeurs rowId
, consultez la documentation de référence sur l'annotation @Insert
, ainsi que la documentation SQLite sur les tables d'ID de ligne.
Annotation "Update"
L'annotation @Update
vous permet de définir des méthodes qui mettent à jour des lignes spécifiques dans une table de base de données. Comme pour @Insert
, les méthodes @Update
acceptent les instances d'entité de données en tant que paramètres.
Le code suivant montre un exemple de méthode @Update
qui tente de mettre à jour un ou plusieurs objets User
dans la base de données :
Kotlin
@Dao interface UserDao { @Update fun updateUsers(vararg users: User) }
Java
@Dao public interface UserDao { @Update public void updateUsers(User... users); }
Room utilise la clé primaire pour faire correspondre les instances d'entités transmises aux lignes de la base de données. S'il n'existe aucune ligne avec la même clé primaire, Room n'effectue aucune modification.
Une méthode @Update
peut éventuellement renvoyer une valeur int
indiquant le nombre de lignes qui ont été correctement mises à jour.
Annotation "Delete"
L'annotation @Delete
vous permet de définir des méthodes qui suppriment des lignes spécifiques d'une table de base de données. Comme pour @Insert
, les méthodes @Delete
acceptent les instances d'entité de données en tant que paramètres.
Le code suivant montre un exemple de méthode @Delete
qui tente de supprimer un ou plusieurs objets User
de la base de données :
Kotlin
@Dao interface UserDao { @Delete fun deleteUsers(vararg users: User) }
Java
@Dao public interface UserDao { @Delete public void deleteUsers(User... users); }
Room utilise la clé primaire pour faire correspondre les instances d'entités transmises aux lignes de la base de données. S'il n'existe aucune ligne avec la même clé primaire, Room n'effectue aucune modification.
Une méthode @Delete
peut éventuellement renvoyer une valeur int
indiquant le nombre de lignes qui ont bien été supprimées.
Méthodes de requête
L'annotation @Query
vous permet d'écrire des instructions SQL et de les exposer en tant que méthodes DAO. Utilisez ces méthodes de requête pour interroger des données de la base de données de votre application, ou lorsque vous devez effectuer des insertions, des mises à jour et des suppressions plus complexes.
Room valide les requêtes SQL au moment de la compilation. Autrement dit, en cas de problème avec votre requête, une erreur de compilation se produit au lieu d'un échec d'exécution.
Requêtes simples
Le code suivant définit une méthode qui utilise une requête SELECT
simple pour renvoyer tous les objets User
de la base de données :
Kotlin
@Query("SELECT * FROM user") fun loadAllUsers(): Array<User>
Java
@Query("SELECT * FROM user") public User[] loadAllUsers();
Les sections suivantes expliquent comment modifier cet exemple pour des cas d'utilisation types.
Afficher un sous-ensemble de colonnes d'une table
La plupart du temps, il vous suffit de renvoyer un sous-ensemble des colonnes de la table que vous interrogez. Par exemple, votre interface utilisateur peut n'afficher que le prénom et le nom d'un utilisateur, plutôt que tous les détails le concernant. Pour économiser les ressources et simplifier l'exécution de votre requête, interrogez uniquement les champs dont vous avez besoin.
Room vous permet de renvoyer un objet simple à partir de l'une de vos requêtes, à condition de mapper l'ensemble de colonnes de résultats avec l'objet renvoyé. Par exemple, vous pouvez définir l'objet suivant pour contenir le prénom et le nom d'un utilisateur :
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; }
Vous pouvez ensuite renvoyer cet objet simple à partir de votre méthode de requête :
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();
Room comprend que la requête renvoie des valeurs pour les colonnes first_name
et last_name
, et que ces valeurs peuvent être mappées avec les champs de la classe NameTuple
. Si la requête renvoie une colonne qui ne correspond à aucun champ dans l'objet renvoyé, Room affiche un avertissement.
Transmettre des paramètres simples à une requête
La plupart du temps, vos méthodes DAO doivent accepter les paramètres pour pouvoir effectuer des opérations de filtrage. Room accepte l'utilisation de paramètres de méthode en tant que paramètres de liaison dans vos requêtes.
Par exemple, le code suivant définit une méthode qui renvoie tous les utilisateurs ayant un âge spécifique :
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);
Vous pouvez également transmettre plusieurs paramètres ou référencer le même paramètre plusieurs fois dans une requête, comme illustré dans le code suivant :
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);
Transmettre une collection de paramètres à une requête
Certaines de vos méthodes DAO peuvent nécessiter la transmission d'un nombre de paramètres variable et inconnu jusqu'à l'exécution. Room comprend quand un paramètre représente une collection et la développe automatiquement au moment de l'exécution en fonction du nombre de paramètres fournis.
Par exemple, le code suivant définit une méthode qui renvoie des informations sur tous les utilisateurs d'un sous-ensemble de régions :
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);
Interroger plusieurs tables
Certaines de vos requêtes peuvent nécessiter l'accès à plusieurs tables pour calculer le résultat. Vous pouvez utiliser des clauses JOIN
dans des requêtes SQL pour référencer plusieurs tables.
Le code suivant définit une méthode qui associe trois tables afin de renvoyer les livres qui sont actuellement prêtés à un utilisateur spécifique :
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);
Vous pouvez également définir des objets simples pour renvoyer un sous-ensemble de colonnes issues de plusieurs tables jointes, comme indiqué dans la section Renvoyer un sous-ensemble de colonnes d'une table. Le code suivant définit un DAO avec une méthode qui renvoie les noms des utilisateurs et les noms des livres qu'ils ont empruntés :
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; } }
Renvoyer un résultat à plusieurs mappages
Dans Room 2.4 et les versions ultérieures, vous pouvez également interroger des colonnes à partir de plusieurs tables sans définir de classe de données supplémentaire en écrivant des méthodes de requête qui renvoient un résultat à plusieurs mappages.
Prenons l'exemple de la section Interroger plusieurs tables.
Au lieu de renvoyer une liste d'instances d'une classe de données personnalisée contenant des associations d'instances User
et Book
, vous pouvez renvoyer un mappage de User
et Book
directement à partir de votre méthode de requête :
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();
Lorsque votre méthode de requête renvoie un résultat à plusieurs mappages, vous pouvez écrire des requêtes qui utilisent des clauses GROUP BY
, ce qui vous permet de profiter des fonctionnalités SQL pour les calculs et les filtres avancés. Par exemple, vous pouvez modifier la méthode loadUserAndBookNames()
pour ne renvoyer que les utilisateurs ayant emprunté trois livres ou plus :
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();
Si vous n'avez pas besoin de mapper des objets entiers, vous pouvez également définir keyColumn
et valueColumn
dans une annotation @MapInfo
au niveau de la méthode de requête pour renvoyer des mappages entre des colonnes spécifiques de votre requête :
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();
Types de retours spéciaux
Room fournit des types de retours spéciaux pour l'intégration avec d'autres bibliothèques d'API.
Requêtes paginées avec la bibliothèque Paging
Room est compatible avec les requêtes paginées grâce à l'intégration dans la bibliothèque Paging. Dans Room 2.3.0-alpha01 et les versions ultérieures, les DAO peuvent renvoyer des objets PagingSource
à utiliser avec 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); }
Pour plus d'informations sur la sélection des paramètres de type pour un élément PagingSource
, consultez Sélectionner des types de clés et de valeurs.
Accès direct du curseur
Si la logique de votre application nécessite un accès direct aux lignes de retour, vous pouvez écrire vos méthodes DAO pour renvoyer un objet Cursor
, comme indiqué dans l'exemple suivant :
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); }
Ressources supplémentaires
Pour en savoir plus sur l'accès aux données à l'aide des DAO de Room, consultez les ressources supplémentaires suivantes :