Escolher tipos de relacionamento entre objetos

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

Blogs