SQLite étant une base de données relationnelle, vous pouvez définir des relations entre les entités. Cependant, alors que la plupart des bibliothèques de mappage relationnel d'objets permettent aux entités de se référencer entre elles, Room interdit explicitement cette pratique. Pour en savoir plus sur le raisonnement technique qui justifie cette décision, consultez Pourquoi Room ne permet pas les références d'objets.
Types de relations
Room est compatible avec les types de relations suivants :
- Un-à-un : représente une relation dans laquelle une seule entité est associée à une autre entité unique.
- Un-à-plusieurs : représente une relation dans laquelle une seule entité peut être associée à plusieurs entités d'un autre type.
- Plusieurs-à-plusieurs : représente une relation dans laquelle plusieurs entités d' un type peuvent être associées à plusieurs entités d'un autre type. Cela nécessite généralement une table de jonction.
- Relations imbriquées à l'aide d'objets intégrés : représente une
relation dans laquelle une entité contient une autre entité en tant que propriété, et
cette entité imbriquée peut contenir d'autres entités. Cela utilise l'annotation
@Embedded.
Choisir entre deux approches
Dans Room, il existe deux façons de définir et d'interroger une relation entre les entités. Vous pouvez utiliser l'une des méthodes suivantes :
- Une classe de données intermédiaire avec des objets intégrés.
- Une fonction de requête relationnelle avec un type renvoyé en multimap.
Si vous n'avez aucune raison spécifique d'utiliser des classes de données intermédiaires, nous vous recommandons d'utiliser le type renvoyé en multimap. Pour en savoir plus sur cette approche, consultez Renvoi en multimap.
L'approche basée sur des classes de données intermédiaires vous permet d'éviter d'écrire des requêtes SQL complexes, mais elle peut également augmenter la complexité du code, car elle nécessite des classes de données supplémentaires. Pour résumer, l'approche liée à un type renvoyé en multimap requiert davantage d'opérations de vos requêtes SQL, tandis que l'approche de classe de données intermédiaire nécessite davantage de travail dans votre code.
Utiliser l'approche de classe de données intermédiaire
Dans l'approche de la classe de données intermédiaire, vous définissez une classe de données qui représente la relation entre vos entités Room. Cette classe de données contient les associations entre les instances d'une entité et les instances d'une autre entité en tant qu'objets intégrés. Vos fonctions de requête peuvent ensuite renvoyer des instances de cette classe de données à utiliser dans votre application.
Par exemple, vous pouvez définir une classe de données UserBook pour représenter les utilisateurs de la bibliothèque avec des livres spécifiques récupérés, et définir une fonction de requête pour récupérer une liste d'instances UserBook à partir de la base de données :
@Dao interface UserBookDao { @Query( """ SELECT user.name AS userName, book.name AS bookName FROM user JOIN book ON user.id = book.user_id """ ) fun loadUserAndBookNames(): LiveData<List<UserBook>> } data class UserBook(val userName: String, val bookName: String)
Utiliser l'approche de type renvoyé en multimap
Avec l'approche de type renvoyé en multimap, vous n'avez pas besoin de définir d'autres classes de données. Spécifiez plutôt un type renvoyé en multimap pour la fonction basée sur la structure de carte que vous souhaitez, ainsi qu'une relation entre vos entités, directement dans votre requête SQL.
Par exemple, la fonction de requête suivante renvoie un mapping d'instances User et Book pour représenter les utilisateurs de la bibliothèque avec des livres spécifiques récupérés :
@Query( """ SELECT * FROM user JOIN book ON user.id = book.user_id """ ) suspend fun loadUserAndBookNames(): Map<User, List<Book>>
Avec les types renvoyés en multimap, vous pouvez également interroger des relations un-à-un qui n'impliquent pas une autre entité. La fonction de requête suivante renvoie un mapping de
User et du nombre de livres qu'ils ont récupérés à l'aide de l'
@MapColumn annotation :
@Query( """ SELECT user.*, COUNT(book.id) AS book_count FROM user LEFT JOIN book ON user.id = book.user_id GROUP BY user.id """ ) suspend fun loadUserAndBookCount(): Map<User, @MapColumn(columnName = "book_count") Int>
Créer des objets intégrés
Parfois, vous souhaitez représenter une entité ou un objet de données comme un ensemble cohérent dans votre logique de base de données, même si l'objet contient plusieurs propriétés. Dans ce genre de
situations, utilisez l'annotation @Embedded pour décomposer un objet en
ses sous-propriétés dans un tableau. Vous pouvez ensuite interroger les propriétés intégrées comme vous le feriez pour d'autres colonnes.
Par exemple, votre classe User peut inclure une propriété Address qui représente une composition des propriétés street, city, state et postCode. Pour stocker les colonnes composées séparément dans le tableau, annotez la propriété Address dans la classe User avec @Embedded. L'extrait de code suivant montre cette configuration :
data class Address( val street: String?, val state: String?, val city: String?, @ColumnInfo(name = "post_code") val postCode: Int ) @Entity data class User( @PrimaryKey val id: Int, val firstName: String, @Embedded val address: Address? )
Le tableau représentant un objet User contient des colonnes portant les noms suivants : id, firstName, street, state, city et post_code.
Si une entité comporte plusieurs propriétés intégrées du même type, vous pouvez conserver
l'unicité de chaque colonne en définissant la prefix propriété. Room ajoute ensuite la valeur définie en tête de chaque colonne dans l'objet intégré.