Como o SQLite é um banco de dados relacional, é possível especificar relações entre entidades. Embora a maioria das bibliotecas de mapeamento relacional permita que objetos de entidade façam referência entre si, o Room proíbe isso explicitamente. Para saber mais sobre o raciocínio técnico por trás dessa decisão, consulte Entender por que o Room não permite referências de objetos.
Tipos de relações
O Room oferece suporte aos seguintes tipos de relações:
- Um para um: representa uma relação em que uma única entidade está relacionada a outra entidade.
- Um para muitos: representa uma relação em que uma única entidade pode estar relacionada a várias entidades de outro tipo.
- Muitos para muitos: representa uma relação em que várias entidades de um tipo podem estar relacionadas a várias entidades de outro tipo. Isso geralmente exige uma tabela de junção.
- Relações aninhadas usando objetos incorporados: representa uma
relação em que uma entidade contém outra entidade como uma propriedade, e
essa entidade aninhada pode conter outras entidades. Isso usa a anotação
@Embedded.
Escolher entre duas abordagens
No Room, existem duas maneiras de definir e consultar uma relação entre entidades. É possível usar:
- Uma classe de dados intermediária com objetos incorporados ou
- Uma função de consulta relacional com um tipo de retorno multimapa.
Se você não tiver um motivo específico para usar classes de dados intermediárias, recomendamos a abordagem do tipo de retorno multimapa. Para saber mais sobre essa abordagem, consulte Retornar um multimapa.
A abordagem de classe de dados intermediária permite evitar a criação de consultas SQL complexas, mas também pode resultar em aumento da complexidade do código porque exige mais classes de dados. Em resumo, a abordagem do tipo de retorno multimapa exige que as consultas SQL executem mais tarefas, ao passo que a abordagem da classe de dados intermediária exige que o código execute mais tarefas.
Usar a abordagem de classe de dados intermediária
Na abordagem de classe de dados intermediária, uma classe de dados, que modela a relação entre as entidades do Room, é definida. Essa classe contém os pareamentos entre instâncias de uma entidade e instâncias de outra entidade como objetos incorporados. As funções de consulta podem retornar instâncias dessa classe de dados para uso no app.
Por exemplo, é possível definir uma classe de dados UserBook para representar usuários de biblioteca que pegaram livros específicos emprestados e definir uma função de consulta para extrair uma lista de instâncias UserBook do banco de dados:
@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)
Usar a abordagem de tipo de retorno multimapa
Na abordagem de tipo de retorno multimapa, não é necessário definir outras classes de dados. Em vez disso, defina um tipo de retorno multimapa para a função com base na estrutura de mapa desejada e defina a relação entre as entidades diretamente na consulta SQL.
Por exemplo, a função de consulta abaixo retorna um mapeamento de instâncias User e Book para representar usuários da biblioteca que pegaram livros específicos emprestados:
@Query( """ SELECT * FROM user JOIN book ON user.id = book.user_id """ ) suspend fun loadUserAndBookNames(): Map<User, List<Book>>
Com os tipos de retorno multimapa, também é possível consultar relações um para um que não envolvem outra entidade. A função de consulta abaixo retorna um mapeamento de
User e o número de livros que eles pegaram emprestados usando a
@MapColumn anotação:
@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>
Criar objetos incorporados
Às vezes, você quer expressar uma entidade ou um objeto de dados como um todo coeso na lógica do banco de dados, mesmo que o objeto tenha várias propriedades. Nessas
situações, use a @Embedded anotação para decompor um objeto nas
subpropriedades em uma tabela. Em seguida, é possível consultar as propriedades incorporadas da mesma forma que faz para outras colunas.
Por exemplo, a classe User pode incluir uma propriedade Address que representa uma composição de propriedades street, city, state e postCode. Para armazenar as colunas compostas separadamente na tabela, anote a propriedade Address na classe User com @Embedded. O snippet de código abaixo mostra essa configuração:
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? )
A tabela que representa um objeto User contém colunas com estes nomes: id, firstName, street, state, city e post_code.
Se uma entidade tiver várias propriedades incorporadas do mesmo tipo, é possível manter
cada coluna exclusiva definindo a prefix propriedade. O Room adiciona o valor fornecido ao início do nome de cada coluna no objeto incorporado.