A fim de interagir com os dados armazenados ao usar a biblioteca de persistência do Room para armazenar dados do app, defina objetos de acesso a dados (DAOs na sigla em inglês). Cada DAO inclui funções que oferecem acesso abstrato ao banco de dados do app. Durante a compilação, o Room gera automaticamente implementações dos DAOs definidos.
Ao usar DAOs para acessar o banco de dados do app em vez de builders de consulta ou consultas diretas, é possível preservar a separação de conceitos, um princípio essencial de arquitetura. Os DAOs também permitem simular o acesso ao banco de dados ao testar o app .
Anatomia de um DAO
É possível definir cada DAO como uma interface ou uma classe abstrata. As interfaces
geralmente são usadas para casos de uso básicos. De qualquer forma, é sempre preciso
adicionar a anotação aos DAOs com @Dao. Eles não têm propriedades, mas definem uma ou mais funções para interagir com os dados no banco de dados do app.
O código abaixo é um exemplo de um DAO que define funções para inserir, excluir e selecionar objetos User em um banco de dados do Room:
@Dao interface UserDao { @Insert suspend fun insertAll(vararg users: User) @Delete suspend fun delete(user: User) @Query("SELECT * FROM user") suspend fun getAll(): List<User> }
Há dois tipos de funções DAO que definem interações com o banco de dados:
- Funções de conveniência, que permitem inserir, atualizar e excluir linhas no banco de dados sem programar códigos SQL.
- Funções de consulta, que permitem criar sua própria consulta SQL para interagir com o banco de dados.
As seções abaixo demonstram como usar os dois tipos de funções DAO para definir as interações de banco de dados de que seu app precisa.
Funções de conveniência
O Room oferece anotações de conveniência para definir funções de inserção, atualização e exclusão sem que você precise programar uma instrução SQL.
Caso precise definir inserções, atualizações ou exclusões mais complexas ou se você precisar consultar os dados no banco de dados, use uma função de consulta em vez disso.
Inserir
A anotação @Insert permite definir funções que inserem os
parâmetros na tabela adequada no banco de dados. O código abaixo mostra exemplos de funções @Insert válidas que inserem um ou mais objetos User no banco de dados:
@Dao interface UserDao { @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insertUsers(vararg users: User) @Insert suspend fun insertBothUsers(user1: User, user2: User) @Insert suspend fun insertUsersAndFriends(user: User, friends: List<User>) }
Cada parâmetro de uma função @Insert precisa ser uma instância de uma classe de entidade de dados Room
com a anotação @Entity ou uma coleção de instâncias de classe da entidade de dados
. Quando uma função @Insert é chamada, o Room insere cada instância de entidade transmitida na tabela de banco de dados correspondente.
Se a função @Insert receber um único parâmetro, ela poderá retornar um valor Long, que é o novo rowId do item inserido. Se o parâmetro for uma matriz ou uma coleção, ele retornará uma matriz ou uma coleção de valores Long, sendo que cada valor corresponde ao rowId de um dos itens inseridos.
Para saber mais sobre como retornar valores rowId, consulte a documentação de referência
da anotação @Insert, e também a documentação do SQLite
para tabelas rowid.
Atualizar
A anotação @Update permite definir funções que atualizam linhas específicas
em uma tabela de banco de dados. Assim como as funções @Insert, as @Update aceitam instâncias de entidade de dados como parâmetros. O código abaixo mostra um exemplo de uma função @Update que tenta atualizar um ou mais objetos User no banco de dados:
@Dao interface UserDao { @Update suspend fun updateUsers(vararg users: User) }
O Room usa a chave primária para encontrar as linhas no banco de dados correspondentes às instâncias de entidade nos argumentos. Caso não haja uma linha com a mesma chave primária, o Room não faz nenhuma modificação.
Uma função @Update tem a opção de retornar um valor Int para indicar o número de linhas que foram corretamente atualizadas.
Excluir
A anotação @Delete permite
definir funções que excluem linhas específicas de uma tabela de banco de dados. Assim como as funções @Insert, as @Delete aceitam instâncias de entidade de dados como parâmetros. O código abaixo mostra um exemplo de uma função @Delete que tenta excluir um ou mais objetos User do banco de dados:
@Dao interface UserDao { @Delete suspend fun deleteUsers(vararg users: User) }
O Room usa a chave primária para encontrar as linhas no banco de dados correspondentes às instâncias de entidade nos argumentos. Caso não haja uma linha com a mesma chave primária, o Room não faz nenhuma modificação.
Uma função @Delete tem a opção de retornar um valor Int para indicar o número de linhas que foram corretamente excluídas.
Inserir
A anotação @Upsert permite
definir funções que inserem instâncias de entidade quando não há uma linha correspondente ou
as atualizam se uma linha já existir com a mesma chave primária.
Assim como as funções @Insert e @Update, as @Upsert aceitam instâncias de entidade de dados como parâmetros. O código abaixo mostra um exemplo de uma função @Upsert que tenta inserir um ou mais objetos User no banco de dados:
@Dao interface UserDao { @Upsert suspend fun upsertUsers(vararg users: User) }
Se a função @Upsert receber um único parâmetro, ela poderá retornar um valor Long. Se isso resultar na inserção de uma nova linha, ela retornará o rowId da linha recém-inserida. Se isso resultar na atualização de uma linha existente, ela retornará -1. Se o parâmetro for uma matriz ou uma coleção, ele retornará uma matriz ou uma coleção de valores Long.
Funções de consulta
A anotação @Query permite criar instruções SQL e expor essas instruções como funções DAO. Use-as para consultar dados no banco do app ou quando for necessário realizar inserções, atualizações e exclusões mais complexas.
O Room valida consultas SQL durante o tempo de compilação. Isso significa que, se houver um problema com a consulta, um erro de compilação é gerado, em vez de uma falha durante a execução.
Consultas simples
O código abaixo define uma função que usa uma consulta SELECT para retornar todos os objetos User no banco de dados:
@Query("SELECT * FROM user") suspend fun loadAllUsers(): List<User>
As seções abaixo demonstram como modificar esse exemplo para casos de uso típicos.
Retornar um subconjunto das colunas de uma tabela
Na maioria das vezes, é necessário retornar apenas um subconjunto das colunas da tabela que você está consultando. Por exemplo, a interface pode exibir apenas o nome e o sobrenome de um usuário, em vez de todos os detalhes sobre ele. Para economizar recursos e otimizar a execução, consulte apenas as propriedades necessárias.
O Room permite que você retorne um objeto de dados de qualquer consulta, desde que o conjunto de colunas de resultados possa ser mapeado para o objeto retornado. Por exemplo, você pode definir o objeto abaixo para armazenar o nome e o sobrenome de um usuário:
data class NameTuple( @ColumnInfo(name = "first_name") val firstName: String, @ColumnInfo(name = "last_name") val lastName: String )
Em seguida, você pode retornar esse objeto de dados da função de consulta:
@Query("SELECT first_name, last_name FROM user") suspend fun loadFullName(): List<NameTuple>
Como a consulta retorna valores para as colunas first_name e last_name, o Room mapeia esses valores para as propriedades na classe NameTuple. Se a consulta retornar uma coluna que não seja mapeada para uma propriedade no objeto retornado, o Room exibe um aviso.
Embora o exemplo anterior use uma classe de dados personalizada para recuperar um subconjunto de colunas, o Room também oferece suporte ao retorno de kotlin.Pair e kotlin.Triple para conveniência quando uma consulta retorna exatamente duas ou três colunas. Ao usar esses tipos, as colunas são mapeadas pela ordem em que são definidas na instrução de consulta. Portanto, a ordem das colunas na instrução SELECT precisa corresponder à ordem dos tipos no Pair ou Triple.
Transmitir parâmetros simples para uma consulta
Na maioria das vezes, as funções DAO precisam aceitar parâmetros para realizar operações de filtragem. O Room oferece suporte ao uso de parâmetros de função como parâmetros de vinculação nas consultas.
Por exemplo, o código abaixo define uma função que retorna todos os usuários acima de uma determinada idade:
@Query("SELECT * FROM user WHERE age > :minAge") suspend fun loadAllUsersOlderThan(minAge: Int): Array<User>
Também é possível transmitir vários parâmetros ou referenciar o mesmo parâmetro várias vezes em uma consulta, conforme mostrado no snippet de código abaixo.
@Query("SELECT * FROM user WHERE age BETWEEN :minAge AND :maxAge") suspend fun loadAllUsersBetweenAges(minAge: Int, maxAge: Int): Array<User> @Query( """ SELECT * FROM user WHERE first_name LIKE :search OR last_name LIKE :search """ ) suspend fun findUserWithName(search: String): List<User>
Transmitir um conjunto de parâmetros para uma consulta
Algumas das funções DAO podem exigir que você transmita um número variável de parâmetros não conhecidos até o momento da execução. Se um parâmetro representar uma coleção, ele será expandido automaticamente no momento da execução com base no número de valores.
Por exemplo, o código abaixo define uma função que retorna informações sobre todos os usuários de um subconjunto de regiões:
@Query("SELECT * FROM user WHERE region IN (:regions)") suspend fun loadUsersFromRegions(regions: List<String>): List<User>
Consulta de várias tabelas
Algumas consultas podem exigir acesso a várias tabelas para calcular o
resultado. É possível usar cláusulas JOIN nas consultas SQL para referenciar mais de uma tabela.
O código abaixo define uma função que une três tabelas para retornar os livros que um usuário específico pegou emprestados:
@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 """ ) suspend fun findBooksBorrowedByName(userName: String): List<Book>
Também é possível definir objetos de dados para retornar um subconjunto de colunas de várias tabelas mescladas. Para mais informações, consulte Retornar um subconjunto das colunas de uma tabela. O código abaixo define um DAO com uma função que retorna os nomes dos usuários e dos livros que eles pegaram emprestados:
interface UserBookDao { @Query( """ SELECT user.name AS userName, book.name AS bookName FROM user, book WHERE user.id = book.user_id """ ) fun loadUserAndBookNames(): Flow<List<UserBook>> } data class UserBook(val userName: String, val bookName: String)
Retornar um multimapa
Para operações de mesclagem, também é possível consultar colunas de várias tabelas sem definir uma classe de dados extra, criando funções de consulta que retornem um multimapa.
Considere o exemplo em Consultar várias tabelas. Em vez de retornar uma lista de instâncias de uma classe de dados personalizada que contém pares de instâncias User e Book, é possível retornar um mapeamento de User e Book diretamente da função de consulta:
@Query( """ SELECT * FROM user JOIN book ON user.id = book.user_id """ ) suspend fun loadUserAndBookNames(): Map<User, List<Book>>
Quando a função de consulta retorna um multimapa, é possível criar consultas que usam cláusulas GROUP BY, o que permite que você aproveite os recursos do SQL para cálculos e filtros avançados. Por exemplo, é possível modificar a função loadUserAndBookNames para retornar apenas usuários com três ou mais livros emprestados:
@Query( """ SELECT * FROM user JOIN book ON user.id = book.user_id GROUP BY user.name HAVING COUNT(book.id) >= 3 """ ) suspend fun loadUserAndBookNamesGrouped(): Map<User, List<Book>>
Caso não precise mapear objetos inteiros, também é possível retornar mapeamentos entre
colunas específicas na consulta usando a anotação @MapColumn nos
parâmetros genéricos do tipo de retorno.
@Query( """ SELECT user.name AS username, book.name AS bookname FROM user JOIN book ON user.id = book.user_id """ ) suspend fun loadUserAndBookNamesColumns(): Map< @MapColumn(columnName = "username") String, List<@MapColumn(columnName = "bookname") String> >
Tipos de retorno especiais
O Room oferece alguns tipos de retorno especiais para integração com outras bibliotecas de API.
Consultas paginadas com a biblioteca Paging
O Room oferece suporte a consultas paginadas usando a integração com a biblioteca Paging. Para usar os tipos de retorno da Paging 3, registre os conversores de tipo de retorno da Paging no banco de dados ou DAO:
- Inclua o artefato
androidx.room3:room3-pagingna configuração de build. - Anote a declaração
@Databaseou@Daocom@DaoReturnTypeConverters(PagingSourceDaoReturnTypeConverter::class).
Depois de registrados, os DAOs podem retornar PagingSource objetos para uso com
a Paging 3:
@Dao @DaoReturnTypeConverters(PagingSourceDaoReturnTypeConverter::class) interface UserDao { @Query("SELECT * FROM users WHERE label LIKE :query") fun pagingSource(query: String): PagingSource<Int, User> }
Veja mais informações sobre como escolher parâmetros de tipo para um PagingSource em
Selecionar tipos de chave e valor.
Acesso direto à conexão do banco de dados
Se a lógica do app exigir acesso direto e de baixo nível à conexão do banco de dados, use as APIs de conexão do Room. É possível fazer uma
conexão usando
useReaderConnection
para operações somente leitura ou
useWriterConnection
para operações de gravação na instância RoomDatabase e usar
usePrepared
para executar instruções:
val result: List<Pair<Long, String>> = roomDatabase.useReaderConnection { connection -> connection.usePrepared( "SELECT * FROM user WHERE age > :minAge LIMIT 5" ) { stmt -> // Bind arguments if needed stmt.bindLong(1, minAge.toLong()) buildList { // Step through the results while (stmt.step()) { add(stmt.getLong(0) to stmt.getText(1)) } } } }
Se você precisar realizar transações de banco de dados de baixo nível diretamente na
conexão, use as funções auxiliares immediateTransaction,
deferredTransaction ou exclusiveTransaction em
uma instância Transactor dentro de um bloco useWriterConnection:
roomDatabase.useWriterConnection { transactor -> transactor.immediateTransaction { // Perform transactional database operations using transactor } }
Como alternativa, se você só precisar executar operações DAO de alto nível em uma
transação, use as withReadTransaction ou withWriteTransaction
funções de extensão auxiliares na instância RoomDatabase:
// Perform transactional read operations (DEFERRED transaction) val userCount = roomDatabase.withReadTransaction { userDao.countUsers() } // Perform transactional write operations (IMMEDIATE transaction) roomDatabase.withWriteTransaction { userDao.insert(newUser) userDao.update(existingUser) }