Para acceder a los datos de tu app con la biblioteca de persistencias Room, trabaja con objetos de acceso a datos o DAO. Este conjunto de objetos Dao
forma el componente principal de Room, ya que cada DAO incluye métodos que ofrecen acceso abstracta a la base de datos de tu app.
Cuando se accede a una base de datos con una clase DAO, en lugar de compiladores de consultas o consultas directas, puedes separar diferentes componentes de la arquitectura de tu base de datos. Además, las clases DAO te permiten simular fácilmente el acceso a la base de datos mientras pruebas tu app.
Una clase DAO puede ser una interfaz o una clase abstracta. Si es una clase abstracta, puede tener opcionalmente un constructor que tome un RoomDatabase
como su único parámetro. Room crea cada implementación de DAO en el momento de la compilación.
Nota: Room no admite el acceso a la base de datos en el subproceso principal, a menos que hayas llamado a allowMainThreadQueries()
en el compilador porque podría bloquear la IU durante un período prolongado. Las consultas asíncronas (que muestran instancias de LiveData
o Flowable
) están exentas de esta regla porque ejecutan la consulta de forma asíncrona cuando es necesario.
Cómo definir métodos para mayor comodidad
Hay múltiples consultas de conveniencia que puedes representar mediante una clase DAO. En este documento, se incluyen varios ejemplos comunes.
Cómo insertar
Cuando creas un método DAO y lo anotas con @Insert
, Room genera una implementación que inserta todos los parámetros en la base de datos en una sola transacción.
En el siguiente fragmento de código, se muestran varias consultas de ejemplo:
Kotlin
@Dao interface MyDao { @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 MyDao { @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); }
Si el método @Insert
recibe solo 1 parámetro, puede mostrar un long
, que es el nuevo rowId
para el elemento insertado. Si el parámetro es un arreglo o una colección, debería mostrar long[]
o List<Long>
.
Para obtener más detalles, consulta la documentación de referencia de la anotación @Insert
y la documentación de SQLite para las tablas rowid.
Cómo actualizar
El método de conveniencia Update
modifica un conjunto de entidades, proporcionadas como parámetros, en la base de datos. Utiliza una consulta que coincide con la clave primaria de cada entidad.
En el siguiente fragmento de código, se muestra cómo definir este método:
Kotlin
@Dao interface MyDao { @Update fun updateUsers(vararg users: User) }
Java
@Dao public interface MyDao { @Update public void updateUsers(User... users); }
Aunque, si bien no suele ser necesario, puedes hacer que este método muestre un valor int
, que indica la cantidad de filas actualizadas en la base de datos.
Cómo borrar
El método de conveniencia Delete
quita un conjunto de entidades, proporcionadas como parámetros, de la base de datos. Utiliza las claves primarias para encontrar las entidades que se borrarán.
En el siguiente fragmento de código, se muestra cómo definir este método:
Kotlin
@Dao interface MyDao { @Delete fun deleteUsers(vararg users: User) }
Java
@Dao public interface MyDao { @Delete public void deleteUsers(User... users); }
Aunque, si bien no suele ser necesario, puedes hacer que este método muestre un valor int
, que indica la cantidad de filas que se quitaron de la base de datos.
Cómo consultar información
@Query
es la anotación principal que se usa en las clases DAO. Te permite realizar operaciones de lectura/escritura en una base de datos. Cada método @Query
se verifica en el momento de la compilación, por lo que si hay un problema con la consulta, se produce un error de compilación en lugar de una falla del entorno de ejecución.
Room también verifica el valor que se muestra de la consulta, de modo que si el nombre del campo del objeto que se muestra no coincide con los nombres de las columnas correspondientes en la respuesta de la consulta, Room te alerta de una de estas dos maneras:
- Muestra una advertencia si solo coinciden algunos nombres de campos.
- Muestra un error si no coinciden los nombres de los campos.
Consultas simples
Kotlin
@Dao interface MyDao { @Query("SELECT * FROM user") fun loadAllUsers(): Array<User> }
Java
@Dao public interface MyDao { @Query("SELECT * FROM user") public User[] loadAllUsers(); }
Esta es una consulta muy simple que carga todos los usuarios. En el momento de compilación, Room sabe que está consultando todas las columnas de la tabla de usuarios. Si la consulta contiene un error de sintaxis o si la tabla de usuarios no existe en la base de datos, Room muestra un error con el mensaje apropiado a medida que se compila tu app.
Cómo pasar parámetros a la consulta
La mayoría de las veces, debes pasar parámetros a consultas con el objetivo de realizar operaciones de filtrado, como mostrar solo usuarios mayores de cierta edad. Para llevar a cabo esta tarea, usa los parámetros del método en tu anotación Room, como se muestra en el siguiente fragmento de código:
Kotlin
@Dao interface MyDao { @Query("SELECT * FROM user WHERE age > :minAge") fun loadAllUsersOlderThan(minAge: Int): Array<User> }
Java
@Dao public interface MyDao { @Query("SELECT * FROM user WHERE age > :minAge") public User[] loadAllUsersOlderThan(int minAge); }
Cuando se procesa esta consulta en el momento de la compilación, Room hace coincidir el parámetro de vinculación :minAge
con el parámetro del método minAge
. Room realiza la coincidencia con los nombres de los parámetros. Si hay una falta de coincidencia, se produce un error al compilar tu app.
También puedes pasar varios parámetros o hacer referencia a ellos varias veces en una consulta, como se muestra en el siguiente fragmento de código:
Kotlin
@Dao interface MyDao { @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
@Dao public interface MyDao { @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); }
Cómo mostrar subconjuntos de columnas
En la mayoría de los casos, solo necesitas obtener algunos campos de una entidad. Por ejemplo, tu IU puede mostrar solo el nombre y el apellido de un usuario, en lugar de mostrar todos sus detalles. Gracias a que obtienes solo las columnas que aparecen en la IU de tu app, ahorras recursos valiosos y la consulta se completa más rápido.
Room te permite mostrar cualquier objeto basado en Java de tus consultas siempre que el conjunto de columnas de resultados se pueda asignar al objeto que se muestra. Por ejemplo, puedes crear el siguiente objeto antiguo y sin formato basado en Java (POJO) para obtener el nombre y apellido del usuario:
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; }
Ahora, puedes usar este POJO en tu método de consulta de la siguiente manera:
Kotlin
@Dao interface MyDao { @Query("SELECT first_name, last_name FROM user") fun loadFullName(): List<NameTuple> }
Java
@Dao public interface MyDao { @Query("SELECT first_name, last_name FROM user") public List<NameTuple> loadFullName(); }
Room comprende que la consulta muestra valores para las columnas first_name
y last_name
, y que se pueden asignar esos valores a los campos de la clase NameTuple
. Por lo tanto, Room puede generar el código correcto. Si la consulta muestra demasiadas columnas o una columna que no existe en la clase NameTuple
, Room muestra una advertencia.
Cómo pasar una colección de argumentos
Es posible que algunas de las consultas requieran que se pase una cantidad variable de parámetros y que no se conozca la cantidad exacta de parámetros hasta el tiempo de ejecución. Por ejemplo, es posible que quieras recuperar información sobre todos los usuarios de un subconjunto de regiones. Room entiende cuándo un parámetro representa una colección y lo expande automáticamente en el tiempo de ejecución en función de la cantidad de parámetros proporcionados.
Kotlin
@Dao interface MyDao { @Query("SELECT first_name, last_name FROM user WHERE region IN (:regions)") fun loadUsersFromRegions(regions: List<String>): List<NameTuple> }
Java
@Dao public interface MyDao { @Query("SELECT first_name, last_name FROM user WHERE region IN (:regions)") public List<NameTuple> loadUsersFromRegions(List<String> regions); }
Cómo acceder directamente al cursor
Si la lógica de tu app requiere acceso directo a las filas mostradas, puedes mostrar un objeto Cursor
de tus consultas, como se indica en el siguiente fragmento de código:
Kotlin
@Dao interface MyDao { @Query("SELECT * FROM user WHERE age > :minAge LIMIT 5") fun loadRawUsersOlderThan(minAge: Int): Cursor }
Java
@Dao public interface MyDao { @Query("SELECT * FROM user WHERE age > :minAge LIMIT 5") public Cursor loadRawUsersOlderThan(int minAge); }
Precaución: No se recomienda trabajar con la API del cursor porque no garantiza que las filas existen o qué valores contienen. Usa esta funcionalidad solo si ya tienes un código que espera un cursor y que no se puede refactorizar fácilmente.
Cómo hacer consultas en varias tablas
Algunas de tus consultas pueden requerir acceso a varias tablas para calcular el resultado. Room te permite escribir cualquier consulta, por lo que también puedes unir tablas.
Además, si la respuesta es un tipo de dato observable, como Flowable
o LiveData
, Room mira todas las tablas a las que se hace referencia en la consulta para la invalidación.
En el siguiente fragmento de código, se muestra cómo realizar una combinación de tablas para consolidar información entre una tabla que contiene usuarios que toman prestados libros y una tabla que contiene datos sobre libros prestados actualmente:
Kotlin
@Dao interface MyDao { @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
@Dao public interface MyDao { @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); }
También puedes mostrar objetos POJO desde estas consultas. Por ejemplo, puedes escribir una consulta que cargue a un usuario y el nombre de su mascota de la siguiente manera:
Kotlin
@Dao interface MyDao { @Query( "SELECT user.name AS userName, pet.name AS petName " + "FROM user, pet " + "WHERE user.id = pet.user_id" ) fun loadUserAndPetNames(): LiveData<List<UserPet>> // You can also define this class in a separate file. data class UserPet(val userName: String?, val petName: String?) }
Java
@Dao public interface MyDao { @Query("SELECT user.name AS userName, pet.name AS petName " + "FROM user, pet " + "WHERE user.id = pet.user_id") public LiveData<List<UserPet>> loadUserAndPetNames(); // You can also define this class in a separate file, as long as you add the // "public" access modifier. static class UserPet { public String userName; public String petName; } }
Tipos de datos que se muestran de consultas
Room admite una variedad de tipos de datos que se muestra para los métodos de consulta, incluidos los tipos de datos que se muestran especializados para la interoperabilidad con API o frameworks específicos. En la siguiente tabla, se muestran los tipos de devolución aplicables según el tipo de consulta y el framework:
Tipo de consulta | Corrutinas | RxJava | Guava | Ciclo de vida |
---|---|---|---|---|
Lectura observable | Flow<T> |
Flowable<T> , Publisher<T> , Observable<T> |
N/D | LiveData<T> |
Lectura de una toma | suspend fun |
Single<T> , Maybe<T> |
ListenableFuture<T> |
N/D |
Escritura por única vez | suspend fun |
Single<T> , Maybe<T> , Completable<T> |
ListenableFuture<T> |
N/D |
Cómo hacer consultas reactivas con Flow
En Room 2.2 y versiones posteriores, puedes asegurarte de que la IU de tu app se mantenga actualizada mediante la funcionalidad Flow
de Kotlin. Para que se actualice automáticamente la IU cuando cambien los datos subyacentes, escribe métodos de consulta que muestren objetos Flow
:
@Query("SELECT * FROM User")
fun getAllUsers(): Flow<List<User>>
Cada vez que cambia alguno de los datos de la tabla, el objeto Flow
que se muestra vuelve a activar la consulta y vuelve a emitir el conjunto de resultados completo.
Las consultas reactivas que usan Flow
tienen una limitación importante: el objeto Flow
vuelve a ejecutar la consulta cada vez que se actualiza una fila de la tabla, independientemente de si está en el conjunto de resultados. Puedes asegurarte de que solo se notifique a la IU cuando cambien los resultados de la consulta mediante la aplicación del operador distinctUntilChanged()
al objeto Flow
mostrado:
@Dao
abstract class UsersDao {
@Query("SELECT * FROM User WHERE username = :username")
abstract fun getUser(username: String): Flow<User>
fun getUserDistinctUntilChanged(username:String) =
getUser(username).distinctUntilChanged()
}
Cómo hacer consultas asíncronas con corrutinas de Kotlin
Puedes agregar la palabra clave suspend
Kotlin a tus métodos DAO para que sean asíncronos mediante la funcionalidad de corrutinas de Kotlin. De esta manera, te aseguras de que no se puedan ejecutar en el subproceso principal.
@Dao
interface MyDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertUsers(vararg users: User)
@Update
suspend fun updateUsers(vararg users: User)
@Delete
suspend fun deleteUsers(vararg users: User)
@Query("SELECT * FROM user")
suspend fun loadAllUsers(): Array<User>
}
Esta guía también se aplica a los métodos DAO anotados con @Transaction
. Puedes usar esta función para compilar métodos de bases de datos de suspensión a partir de otros métodos DAO. Estos métodos se ejecutan en una sola transacción de base de datos.
@Dao
abstract class UsersDao {
@Transaction
open suspend fun setLoggedInUser(loggedInUser: User) {
deleteUser(loggedInUser)
insertUser(loggedInUser)
}
@Query("DELETE FROM users")
abstract fun deleteUser(user: User)
@Insert
abstract suspend fun insertUser(user: User)
}
Para obtener más información sobre el uso de las corrutinas de Kotlin en tu app, consulta Cómo mejorar el rendimiento de la app con corrutinas de Kotlin.
Cómo hacer consultas observables con LiveData
Cuando realices consultas, a menudo desearás que se actualice automáticamente la IU de tu app cuando cambien los datos. Para lograrlo, usa un valor de retorno de tipo LiveData
en la descripción del método de consulta. Room genera todo el código necesario para actualizar el LiveData
cuando se actualiza la base de datos.
Kotlin
@Dao interface MyDao { @Query("SELECT first_name, last_name FROM user WHERE region IN (:regions)") fun loadUsersFromRegionsSync(regions: List<String>): LiveData<List<User>> }
Java
@Dao public interface MyDao { @Query("SELECT first_name, last_name FROM user WHERE region IN (:regions)") public LiveData<List<User>> loadUsersFromRegionsSync(List<String> regions); }
Cómo hacer consultas reactivas con RxJava
Room proporciona la siguiente compatibilidad con valores de muestra de tipos RxJava2:
- Métodos
@Query
: Room admite valores de retorno de tipoPublisher
,Flowable
yObservable
. - Métodos
@Insert
,@Update
y@Delete
: Room 2.1.0 y versiones posteriores admiten valores de tipoCompletable
,Single<T>
yMaybe<T>
.
Para usar esta funcionalidad, incluye la versión más reciente del artefacto rxjava2
en el archivo build.gradle
de tu app:
app/build.gradle
dependencies { def room_version = "2.1.0" implementation 'androidx.room:room-rxjava2:$room_version' }
Para ver las versiones actuales de esta biblioteca, consulta la información sobre Room en la página de versiones.
En el siguiente fragmento de código, se muestran varios ejemplos de cómo podrías usar estos tipos de datos que se muestran:
Kotlin
@Dao interface MyDao { @Query("SELECT * from user where id = :id LIMIT 1") fun loadUserById(id: Int): Flowable<User> // Emits the number of users added to the database. @Insert fun insertLargeNumberOfUsers(users: List<User>): Maybe<Int> // Makes sure that the operation finishes successfully. @Insert fun insertLargeNumberOfUsers(varargs users: User): Completable /* Emits the number of users removed from the database. Always emits at least one user. */ @Delete fun deleteAllUsers(users: List<User>): Single<Int> }
Java
@Dao public interface MyDao { @Query("SELECT * from user where id = :id LIMIT 1") public Flowable<User> loadUserById(int id); // Emits the number of users added to the database. @Insert public Maybe<Integer> insertLargeNumberOfUsers(List<User> users); // Makes sure that the operation finishes successfully. @Insert public Completable insertLargeNumberOfUsers(User... users); /* Emits the number of users removed from the database. Always emits at least one user. */ @Delete public Single<Integer> deleteUsers(List<User> users); }
Para obtener información detallada, consulta el artículo de Google Developers Room y RxJava.
Recursos adicionales
Para obtener más información sobre cómo acceder a datos mediante DAO de sala, consulta los siguientes recursos adicionales.
Ejemplos
- Android Sunflow
- Ejemplo de Room y RxJava (Java) (Kotlin)