Accéder aux données à l'aide des DAO Room

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.

Mettre à jour

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.

Supprimer

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 :

Exemples

Ateliers de programmation