Como o SQLite é um banco de dados relacional, é possível especificar relações entre entidades. Embora a maioria das bibliotecas de mapeamento relacional de objetos permita que objetos de entidade se referenciem mutuamente, 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 relacionamento:
- Um para um: representa uma relação em que uma única entidade está relacionada a outra única entidade.
- Um para muitos: representa uma relação em que uma única entidade pode ser relacionada a várias entidades de outro tipo.
- Muitos para muitos: representa uma relação em que várias entidades de um tipo podem ser relacionadas a várias entidades de outro tipo. Isso geralmente exige uma tabela de junção.
- Relacionamentos aninhados (usando objetos incorporados): representa um
relacionamento em que uma entidade contém outra como um campo, 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. Você pode usar:
- Uma classe de dados intermediária com objetos incorporados ou
- Um método 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 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. Os métodos 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 um método de consulta para extrair uma lista
de instâncias UserBook
do banco de dados:
Kotlin
@Dao
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>>
}
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();
}
public class UserBook {
public String userName;
public String bookName;
}
Usar a abordagem de tipos 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 o método com base na estrutura de mapa desejada e defina a relação entre as entidades diretamente na consulta SQL.
Por exemplo, o método de consulta abaixo retorna um mapeamento de instâncias User
e Book
para representar usuários da biblioteca que pegaram livros específicos emprestados:
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();
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ários campos. Nessas
situações, é possível usar a anotação @Embedded
para representar um objeto
que você gostaria de decompor nos subcampos em uma tabela. Em seguida, é possível consultar os campos incorporados da mesma forma que faria para outras colunas individuais.
Por exemplo, a classe User
pode incluir um campo do tipo Address
que
representa uma composição de campos com os nomes street
, city
, state
e
postCode
. Para armazenar as colunas compostas separadamente na tabela, inclua um
campo Address
. Ele precisa aparecer na classe User
com a anotação
@Embedded
. O snippet de código abaixo demonstra isso:
Kotlin
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?
)
Java
public class Address {
public String street;
public String state;
public String city;
@ColumnInfo(name = "post_code") public int postCode;
}
@Entity
public class User {
@PrimaryKey public int id;
public String firstName;
@Embedded public 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 tem vários campos incorporados do mesmo tipo, é possível manter cada
coluna única, definindo a propriedade prefix
. O Room adiciona o
valor fornecido ao início do nome de cada coluna no objeto incorporado.
Outros recursos
Para saber mais sobre como definir relações entre entidades no Room, consulte estes recursos abaixo.
Vídeos
- O que há de novo no Room (Conferência de Desenvolvedores Android, 2019)