Como SQLite es una base de datos relacional, puedes definir relaciones entre entidades. Aunque la mayoría de las bibliotecas de asignación relacional de objetos permiten que los objetos de entidad se hagan referencia entre sí, Room lo prohíbe explícitamente. Para obtener más información sobre el razonamiento técnico que respalda esta decisión, consulta Por qué Room no permite referencias a objetos.
Tipos de relaciones
Room admite los siguientes tipos de relaciones:
- Uno a uno: Representa una relación en la que una sola entidad se relaciona con otra entidad única.
- Uno a varios: Representa una relación en la que una sola entidad se puede relacionar con varias entidades de otro tipo.
- Varios a varios: Representa una relación en la que varias entidades de un tipo se pueden relacionar con varias entidades de otro tipo. Por lo general, esto requiere una tabla de unión.
- Relaciones anidadas con objetos incorporados: Representa una
relación en la que una entidad contiene otra entidad como una propiedad, y
esta entidad anidada puede contener otras entidades. Esto usa la anotación
@Embedded.
Elige entre dos enfoques
En Room, hay dos formas de definir y realizar búsquedas de una relación entre entidades. Puedes usar cualquiera de las siguientes opciones:
- Una clase de datos intermedia con objetos incorporados
- Una función de búsqueda relacional con un tipo de datos que se devuelven de multimapa
Si no tienes un motivo específico para usar clases de datos intermedios, te recomendamos que uses el enfoque que muestra tipos de datos que se devuelven de multimapa. Para obtener más información sobre este enfoque, consulta Cómo devolver un multimapa.
El enfoque de clase de datos intermedio te permite evitar escribir consultas en SQL complejas, pero también puede dar como resultado una mayor complejidad del código porque requiere clases de datos adicionales. En resumen, el enfoque que muestra tipos de datos que se devuelven de multimapa requiere que tus consultas en SQL hagan más trabajo; y el enfoque de clase de datos intermedio requiere que el código realice más trabajo.
Usa el enfoque de clase de datos intermedio
En el enfoque de clase de datos intermedio, puedes definir una clase de datos que modela la relación entre tus entidades de Room. Esta clase de datos contiene las vinculaciones entre instancias de una entidad e instancias de otra entidad como objetos incorporados. Los métodos de búsqueda pueden devolver instancias de esta clase de datos para usar en tu app.
Por ejemplo, puedes definir una clase de datos UserBook para representar a los usuarios de la biblioteca que retiraron determinados libros y definir una función de búsqueda para recuperar una lista de instancias de UserBook de la base de datos:
@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)
Usa el enfoque que muestra tipos de datos que se devuelven de multimapa
En el método que muestra tipos de datos que se devuelven de multimapa, no necesitas definir ninguna clase de datos adicional. En su lugar, defines el tipo de datos que se devuelven de multimapa para tu función según la estructura de mapa que deseas y que define la relación entre tus entidades directamente en tu consulta en SQL.
Por ejemplo, la siguiente función de búsqueda devuelve una asignación de las instancias User y Book para representar a los usuarios de la biblioteca que retiraron determinados libros:
@Query( """ SELECT * FROM user JOIN book ON user.id = book.user_id """ ) suspend fun loadUserAndBookNames(): Map<User, List<Book>>
Con los tipos de datos que se devuelven de multimapa, también puedes consultar relaciones uno a uno que no involucren a otra entidad. La siguiente función de búsqueda devuelve una asignación de
User y la cantidad de libros que retiraron con la
@MapColumn anotación:
@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>
Cómo crear objetos incorporados
Es posible que, a veces, quieras expresar una entidad o un objeto de datos como un solo elemento integral en la lógica de la base de datos, incluso si el objeto contiene varias propiedades. En esas
situaciones, usa la @Embedded anotación para desglosar un objeto en
sus subpropiedades dentro de una tabla. Luego, puedes consultar las propiedades integradas tal como lo harías con otras columnas.
Por ejemplo, la clase User puede incluir una propiedad Address que representa una composición de propiedades street, city, state y postCode. Para almacenar las columnas compuestas por separado en la tabla, anota la propiedad Address en la clase User con @Embedded. En el siguiente fragmento de código, se muestra esta configuración:
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? )
La tabla que representa un objeto User contiene columnas con los siguientes nombres: id, firstName, street, state, city y post_code.
Si una entidad tiene varias propiedades incorporadas del mismo tipo, puedes mantener
cada columna única mediante la configuración de la propiedad prefix. Luego, Room agrega el valor proporcionado al comienzo de cada nombre de columna en el objeto incorporado.