Como acessar dados usando DAOs do Room

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 métodos 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 consultas diretas ou builders de consulta, é possível preservar a separação de conceitos, um princípio essencial de arquitetura. Os DAOs também facilitam a simulação do 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 @Dao aos DAOs. Eles não têm propriedades, mas definem um ou mais métodos para interagir com os dados no banco de dados do app.

O código abaixo é um exemplo de um DAO simples que define métodos para inserir, excluir e selecionar objetos User em um banco de dados do 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();
}

Existem dois tipos de métodos DAO que definem interações de banco de dados:

  • Métodos de conveniência, que permitem inserir, atualizar e excluir linhas no banco de dados sem programar códigos SQL.
  • Métodos 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 métodos DAO para definir as interações de banco de dados necessárias do app.

Métodos de conveniência

O Room oferece anotações de conveniência para definir métodos de inserção, atualização e exclusão simples sem que você precise programar uma instrução SQL.

Caso precise definir inserções, atualizações ou exclusões mais complexas ou consultar os dados no banco de dados, use um método de consulta.

Inserir

A anotação @Insert permite definir métodos que inserem os parâmetros na tabela adequada no banco de dados. O código abaixo mostra exemplos de métodos @Insert válidos que inserem um ou mais objetos User no banco de dados:

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 de um método @Insert precisa ser uma instância de uma classe de entidade de dados do Room com a anotação @Entity ou uma coleção de instâncias de classe de entidade de dados, cada uma apontando para um banco de dados. Quando um método @Insert é chamado, o Room insere cada instância de entidade transmitida na tabela de banco de dados correspondente.

Se o método @Insert receber um único parâmetro, ele pode retornar um valor long, que é o novo rowId do item inserido. Se o parâmetro for uma matriz ou coleção, o método retorna uma matriz ou 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 (em inglês).

Atualizar

A anotação @Update permite definir métodos que atualizam linhas específicas em uma tabela de banco de dados. Assim como os métodos @Insert, os @Update aceitam instâncias de entidade de dados como parâmetros. O código abaixo mostra um exemplo de um método @Update que tenta atualizar um ou mais objetos User no banco de dados:

Kotlin

@Dao
interface UserDao {
    @Update
    fun updateUsers(vararg users: User)
}

Java

@Dao
public interface UserDao {
    @Update
    public void updateUsers(User... users);
}

O Room usa a chave primária para encontrar as linhas no banco de dados correspondentes às instâncias de entidade transmitidas. Caso não haja uma linha com a mesma chave primária, o Room não faz nenhuma modificação.

Um método @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 métodos que excluem linhas específicas de uma tabela de banco de dados. Assim como os métodos @Insert, os @Delete aceitam instâncias de entidade de dados como parâmetros. O código abaixo mostra um exemplo de um método @Delete que tenta excluir um ou mais objetos User do banco de dados:

Kotlin

@Dao
interface UserDao {
    @Delete
    fun deleteUsers(vararg users: User)
}

Java

@Dao
public interface UserDao {
    @Delete
    public void deleteUsers(User... users);
}

O Room usa a chave primária para encontrar as linhas no banco de dados correspondentes às instâncias de entidade transmitidas. Caso não haja uma linha com a mesma chave primária, o Room não faz nenhuma modificação.

Um método @Delete tem a opção de retornar um valor int para indicar o número de linhas que foram corretamente excluídas.

Métodos de consulta

A anotação @Query permite criar instruções SQL e expor essas instruções como métodos DAO. Use-os 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 a 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 um método que usa uma consulta SELECT simples para retornar todos os objetos User do banco de dados:

Kotlin

@Query("SELECT * FROM user")
fun loadAllUsers(): Array<User>

Java

@Query("SELECT * FROM user")
public User[] loadAllUsers();

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, você só precisa retornar um subconjunto de colunas da tabela consultada. 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 os campos necessários.

O Room permite que você retorne qualquer objeto de consultas, 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:

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;
}

Em seguida, você pode retornar esse objeto simples do método de consulta:

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();

O Room entende que a consulta retorna valores para as colunas de first_name e last_name e que esses valores podem ser mapeados para os campos da classe NameTuple. Se a consulta retornar uma coluna que não seja mapeada para um campo no objeto retornado, o Room exibe um aviso.

Transmitir parâmetros simples para uma consulta

Na maioria das vezes, os métodos DAO precisam aceitar parâmetros para realizar operações de filtragem. O Room oferece suporte ao uso de parâmetros de método como parâmetros de vinculação nas consultas.

Por exemplo, o código abaixo define um método que retorna todos os usuários acima de uma determinada idade:

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);

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.

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);

Transmitir um conjunto de parâmetros para uma consulta

Alguns dos métodos DAO podem exigir que você transmita um número variável de parâmetros não conhecidos até o momento da execução. O Room entende quando um parâmetro representa uma coleção e o expande automaticamente durante a execução, com base no número de parâmetros fornecidos.

Por exemplo, o código abaixo define um método que retorna informações sobre todos os usuários de um subconjunto de regiões:

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);

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 um método que une três tabelas para retornar os livros que um usuário específico pegou emprestados:

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);

Também é possível definir objetos simples para retornar um subconjunto de colunas de várias tabelas mescladas, conforme discutido em Retornar um subconjunto de colunas de uma tabela. O código abaixo define um DAO com um método que retorna os nomes dos usuários e os nomes dos livros que eles pegaram emprestados:

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;
   }
}

Retornar um multimapa

No Room 2.4 e versões mais recentes, também é possível consultar colunas de várias tabelas sem definir uma classe de dados extra, criando métodos de consulta que retornem um multimapa (link em inglês).

Considere o exemplo na seção 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 do método de consulta:

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();

Quando o método 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, você pode modificar o método loadUserAndBookNames() para retornar apenas usuários com três ou mais livros emprestados:

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();

Caso não precise mapear objetos inteiros, também é possível retornar mapeamentos entre colunas específicas na consulta, definindo os atributos keyColumn e valueColumn em uma anotação @MapInfo no método de consulta:

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 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. No Room 2.3.0-alpha01 e versões mais recentes, os DAOs podem retornar objetos PagingSource para uso com a 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);
}

Veja mais informações sobre como escolher parâmetros de tipo para um PagingSource em Selecionar tipos de chave e valor.

Acesso direto com cursor

Se a lógica do app exigir acesso direto às linhas de retorno, programe os métodos DAO para retornar um objeto Cursor, conforme mostrado no exemplo abaixo.

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);
}

Outros recursos

Para saber mais sobre como acessar dados usando DAOs do Room, consulte os recursos abaixo:

Exemplos

Codelabs