Cuando usas la biblioteca de persistencias Room para almacenar los datos de tu app, interactúas con los datos almacenados a través de la definición de objetos de acceso a datos o DAO. Cada DAO incluye métodos que ofrecen acceso abstracto a la base de datos de tu app. En el tiempo de compilación, Room genera automáticamente implementaciones de los DAOs que definas.
Si usas DAOs para acceder a la base de datos de tu app en lugar de compiladores de búsquedas o búsquedas directas, puedes conservar la separación de problemas, un principio arquitectónico importante. Los DAOs también te permiten simular con mayor facilidad el acceso a la bases de datos cuando pruebas tu app.
Anatomía de un DAO
Puedes definir cada DAO como una interfaz o una clase abstracta. Por lo general, debes usar una interfaz para casos de uso básicos. En cualquier caso, siempre debes anotar tus DAOs con @Dao
. Los DAOs no tienen propiedades, pero definen uno o más métodos para interactuar con los datos de la base de datos de tu app.
El siguiente código es un ejemplo de un DAO simple que define métodos para insertar, borrar y seleccionar objetos User
en una base de datos de 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(); }
Existen dos tipos de métodos DAO que definen interacciones de bases de datos:
- Métodos de conveniencia que te permiten insertar, actualizar y borrar filas en tu base de datos sin escribir ningún código de SQL.
- Métodos de búsqueda que te permiten escribir tu propia consulta en SQL para interactuar con la base de datos.
En las siguientes secciones, se muestra el modo para usar ambos tipos de métodos DAO y así definir las interacciones de la base de datos que necesita tu app.
Métodos de conveniencia
Room proporciona anotaciones de conveniencia para definir métodos que permiten realizar inserciones, actualizaciones y eliminaciones simples sin necesidad de escribir una instrucción de SQL.
Si necesitas definir inserciones, actualizaciones o eliminaciones más complejas, o bien quieres consultar los datos de la base de datos, usa un método de búsqueda.
Inserción
La anotación @Insert
te permite definir métodos que insertan sus parámetros en la tabla adecuada en la base de datos. En el siguiente código, se muestran ejemplos de métodos @Insert
válidos que insertan uno o más objetos User
en la base de datos:
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); }
Cada parámetro para un método @Insert
debe ser una instancia de una clase de entidad de datos de Room con @Entity
como anotación o una colección de instancias de clase de entidad de datos, cada una de las cuales dirige a una base de datos. Cuando se llama a un método @Insert
, Room inserta cada instancia de entidad pasada en la tabla de base de datos correspondiente.
Si el método @Insert
recibe un solo parámetro, puede mostrar un valor long
, que es el nuevo rowId
para el elemento insertado. Si el parámetro es un array o una colección, muestra un array o una colección de valores long
en su lugar, donde cada valor debe ser el rowId
de uno de los elementos insertados. A fin de obtener más información sobre los valores rowId
que se muestran, consulta la documentación de referencia de la anotación @Insert
y la documentación de SQLite para tablas de rowid.
Actualizar
La anotación @Update
te permite definir métodos que actualizan filas específicas en una tabla de base de datos. Al igual que los métodos @Insert
, los métodos @Update
aceptan instancias de entidades de datos como parámetros.
El siguiente código muestra un ejemplo de un método @Update
que intenta actualizar uno o más objetos User
en la base de datos:
Kotlin
@Dao interface UserDao { @Update fun updateUsers(vararg users: User) }
Java
@Dao public interface UserDao { @Update public void updateUsers(User... users); }
Room usa la clave primaria para hacer coincidir las instancias de entidades pasadas con las filas de la base de datos. Si no hay una fila con la misma clave primaria, Room no realiza cambios.
De manera opcional, un método @Update
puede mostrar un valor int
que indica la cantidad de filas que se actualizaron de forma correcta.
Borrar
La anotación @Delete
te permite definir métodos que borran filas específicas de una tabla de base de datos. Al igual que los métodos @Insert
, los métodos @Delete
aceptan instancias de entidades de datos como parámetros.
En el siguiente código, se muestra un ejemplo de un método @Delete
que intenta borrar uno o más objetos User
de la base de datos:
Kotlin
@Dao interface UserDao { @Delete fun deleteUsers(vararg users: User) }
Java
@Dao public interface UserDao { @Delete public void deleteUsers(User... users); }
Room usa la clave primaria para hacer coincidir las instancias de entidades pasadas con las filas de la base de datos. Si no hay una fila con la misma clave primaria, Room no realiza cambios.
De manera opcional, un método @Delete
puede mostrar un valor int
que indique la cantidad de filas que se borraron de forma correcta.
Métodos de búsqueda
La anotación @Query
te permite escribir instrucciones de SQL y exponerlas como métodos DAO. Usa estos métodos de búsqueda para consultar datos desde la base de datos de tu app o cuando necesites realizar inserciones, actualizaciones y eliminaciones más complejas.
Room valida las consultas en SQL en el tiempo de compilación. Esto significa que, si hay un problema con tu búsqueda, se produce un error de compilación en lugar de una falla del tiempo de ejecución.
Búsquedas simples
Con el siguiente código, se define un método que usa una búsqueda SELECT
simple para mostrar todos los objetos User
de la base de datos:
Kotlin
@Query("SELECT * FROM user") fun loadAllUsers(): Array<User>
Java
@Query("SELECT * FROM user") public User[] loadAllUsers();
En las siguientes secciones, se muestra cómo modificar este ejemplo para casos de uso típicos.
Cómo mostrar un subconjunto de columnas de una tabla
En la mayoría de los casos, solo necesitas mostrar un subconjunto de las columnas de la tabla que consultas. Por ejemplo, tu IU podría mostrar solo el nombre y el apellido de un usuario, en lugar de todos los detalles sobre ese usuario. Para ahorrar recursos y optimizar la ejecución de tu búsqueda, solo debes consultar los campos que necesitas.
Room te permite mostrar un objeto simple de cualquiera de tus búsquedas, siempre y cuando puedas asignar el conjunto de columnas de resultados al objeto que se muestra. Por ejemplo, puedes definir el siguiente objeto para conservar el nombre y apellido de un 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; }
Luego, puedes mostrar ese objeto simple desde tu método de búsqueda:
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 comprende que la búsqueda muestra valores para las columnas first_name
y last_name
, y que se pueden asignar esos valores a los campos de la clase NameTuple
. Si la búsqueda muestra una columna que no se asigna a un campo en el objeto que se muestra, Room muestra una advertencia.
Cómo pasar parámetros simples a una búsqueda
La mayoría de las veces, los métodos DAO deben aceptar parámetros para que puedan realizar operaciones de filtrado. Room admite el uso de parámetros de métodos como parámetros de vinculación en tus búsquedas.
Por ejemplo, el siguiente código define un método que muestra todos los usuarios mayores de cierta edad:
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);
También puedes pasar varios parámetros o hacer referencia al mismo parámetro varias veces en una búsqueda, como se muestra en el siguiente código:
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);
Cómo pasar una colección de parámetros a una búsqueda
Es posible que algunos de tus métodos DAO requieran que pases una cantidad variable de parámetros que no se conocen hasta el tiempo de ejecución. 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.
Por ejemplo, el siguiente código define un método que muestra información sobre todos los usuarios de un subconjunto de regiones:
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);
Cómo realizar búsquedas en varias tablas
Algunas de tus búsquedas pueden requerir acceso a varias tablas para calcular el resultado. Puedes usar cláusulas JOIN
en tus consultas en SQL para hacer referencia a más de una tabla.
El siguiente código define un método que une tres tablas para mostrar los libros que se prestaron a un usuario específico:
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);
También puedes definir objetos simples para mostrar un subconjunto de columnas de varias tablas unidas, como se describe en Cómo mostrar un subconjunto de columnas de una tabla. El siguiente código define un DAO con un método que muestra los nombres de los usuarios y de los libros que se prestaron:
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; } }
Cómo mostrar un multimapa
En Room 2.4 y versiones posteriores, también puedes realizar búsquedas en columnas de varias tablas sin definir una clase de datos adicional mediante la escritura de métodos de búsqueda que muestren un multimapa.
Considera el ejemplo de la sección Cómo realizar búsquedas en varias tablas.
En lugar de mostrar una lista de instancias de una clase de datos personalizada que contenga pares de instancias User
y Book
, puedes mostrar una asignación de User
y Book
directamente desde tu método de búsqueda:
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();
Cuando tu método de búsqueda muestra un multimapa, puedes escribir búsquedas que usen cláusulas GROUP BY
, lo que te permite aprovechar las capacidades de SQL para los cálculos avanzados y el filtrado. Por ejemplo, puedes modificar el método loadUserAndBookNames()
para que solo se muestren usuarios recurrentes que retiraron tres o más libros:
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 no necesitas asignar objetos enteros, también puedes mostrar asignaciones entre columnas específicas de tu búsqueda si configuras los atributos keyColumn
y valueColumn
en una anotación @MapInfo
en tu método de búsqueda:
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();
Tipos de datos especiales que se muestran
Room proporciona algunos tipos de datos especiales que se muestran para la integración con otras bibliotecas de API.
Búsquedas paginadas con la biblioteca de Paging
Room admite búsquedas paginadas a través de la integración con la biblioteca de Paging. En Room 2.3.0-alpha01 y versiones posteriores, los DAOs pueden mostrar objetos PagingSource
para usar con 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); }
Si quieres obtener información para elegir parámetros de tipo para un PagingSource
, consulta Cómo seleccionar tipos de clave y de valor.
Cómo acceder directamente al cursor
Si la lógica de tu app requiere acceso directo a las filas que se muestran, puedes escribir tus métodos DAO para mostrar un objeto Cursor
como se indica en el siguiente ejemplo:
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); }
Recursos adicionales
Para obtener más información sobre cómo acceder a datos mediante DAOs de Room, consulta los siguientes recursos adicionales.