Definir relações entre objetos

Como o SQLite é um banco de dados relacional, é possível especificar relações entre objetos. Embora a maior parte das bibliotecas de mapeamento relacional de objetos permita que objetos de entidades 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 Entenda por que o Room não permite referências de objetos.

Definir relações "um para muitos"

Embora não seja possível usar relações diretas, o Room ainda permite que você defina restrições de chave externa entre entidades.

Por exemplo, se há outra entidade chamada Book, você pode definir sua relação com a entidade User usando a anotação @ForeignKey, conforme mostrado no snippet de código a seguir.

Kotlin

    @Entity(foreignKeys = arrayOf(ForeignKey(
                entity = User::class,
                parentColumns = arrayOf("id"),
                childColumns = arrayOf("user_id"))
           )
    )
    data class Book(
        @PrimaryKey val bookId: Int,
        val title: String?,
        @ColumnInfo(name = "user_id") val userId: Int
    )
    

Java

    @Entity(foreignKeys = @ForeignKey(entity = User.class,
                                      parentColumns = "id",
                                      childColumns = "user_id"))
    public class Book {
        @PrimaryKey public int bookId;

        public String title;

        @ColumnInfo(name = "user_id") public int userId;
    }
    

Como zero ou mais instâncias do Book podem ser ligadas a uma única instância do User por meio da chave externa user_id, isso modela uma relação "um para muitos" entre User e Book.

As chaves externas são muito eficientes porque possibilitam especificar o que ocorre quando a entidade referenciada é atualizada. Por exemplo, você poderá mandar o SQLite apagar todos os "books" de um "user" se a instância correspondente do User for excluída, incluindo onDelete = CASCADE na anotação @ForeignKey.

Criar objetos aninhados

À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, você pode usar a anotação @Embedded para representar um objeto que você gostaria de decompor nos subcampos em uma tabela. Em seguida, você pode consultar os campos incorporados da mesma forma que faria para outras colunas individuais.

Por exemplo, sua classe User pode incluir um campo do tipo Address, que representa uma composição de campos nomeados street, city, state e postCode. Para armazenar as colunas compostas separadamente na tabela, inclua um campo Address na classe User que é anotada com @Embedded, conforme mostrado no snippet de código a seguir.

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 os seguintes 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.

Definir relações "muitos para muitos"

Há outro tipo de relação que muitas vezes precisa ser modelada em um banco de dados relacional: uma relação "muitos para muitos" entre duas entidades, em que cada entidade pode ser vinculada a zero ou mais instâncias da outra. Por exemplo, considere um app de streaming de música no qual os usuários podem organizar suas músicas preferidas em playlists. As playlists podem ter um número ilimitado de músicas, e cada música pode ser incluída em quantas playlists você quiser.

Para modelar essa relação, você precisará criar três objetos:

  1. Uma classe de entidade para as playlists.
  2. Uma classe de entidade para as músicas.
  3. Uma classe intermediária para armazenar as informações sobre quais músicas estão em quais playlists.

É possível definir as classes de entidade como unidades independentes:

Kotlin

    @Entity
    data class Playlist(
        @PrimaryKey var id: Int,
        val name: String?,
        val description: String?
    )

    @Entity
    data class Song(
        @PrimaryKey var id: Int,
        val songName: String?,
        val artistName: String?
    )
    

Java

    @Entity
    public class Playlist {
        @PrimaryKey public int id;

        public String name;
        public String description;
    }

    @Entity
    public class Song {
        @PrimaryKey public int id;

        public String songName;
        public String artistName;
    }
    

Em seguida, defina a classe intermediária como uma entidade que contém referências de chave externa para Song e Playlist:

Kotlin

    @Entity(tableName = "playlist_song_join",
            primaryKeys = arrayOf("playlistId","songId"),
            foreignKeys = arrayOf(
                             ForeignKey(entity = Playlist::class,
                                        parentColumns = arrayOf("id"),
                                        childColumns = arrayOf("playlistId")),
                             ForeignKey(entity = Song::class,
                                        parentColumns = arrayOf("id"),
                                        childColumns = arrayOf("songId"))
                                  )
            )
    data class PlaylistSongJoin(
        val playlistId: Int,
        val songId: Int
    )
    

Java

    @Entity(tableName = "playlist_song_join",
            primaryKeys = { "playlistId", "songId" },
            foreignKeys = {
                    @ForeignKey(entity = Playlist.class,
                                parentColumns = "id",
                                childColumns = "playlistId"),
                    @ForeignKey(entity = Song.class,
                                parentColumns = "id",
                                childColumns = "songId")
                    })
    public class PlaylistSongJoin {
        public int playlistId;
        public int songId;
    }
    

Isso gera um modelo de relação "muitos para muitos" que permite que você use um DAO para consultar tanto as playlists por música quanto as músicas por playlist:

Kotlin

    @Dao
    interface PlaylistSongJoinDao {
        @Insert
        fun insert(playlistSongJoin: PlaylistSongJoin)

        @Query("""
               SELECT * FROM playlist
               INNER JOIN playlist_song_join
               ON playlist.id=playlist_song_join.playlistId
               WHERE playlist_song_join.songId=:songId
               """)
        fun getPlaylistsForSong(songId: Int): Array<Playlist>

        @Query("""
               SELECT * FROM song
               INNER JOIN playlist_song_join
               ON song.id=playlist_song_join.songId
               WHERE playlist_song_join.playlistId=:playlistId
               """)
        fun getSongsForPlaylist(playlistId: Int): Array<Song>
    }
    

Java

    @Dao
    public interface PlaylistSongJoinDao {
        @Insert
        void insert(PlaylistSongJoin playlistSongJoin);

        @Query("SELECT * FROM playlist " +
               "INNER JOIN playlist_song_join " +
               "ON playlist.id=playlist_song_join.playlistId " +
               "WHERE playlist_song_join.songId=:songId")
        List<Playlist> getPlaylistsForSong(final int songId);

        @Query("SELECT * FROM song " +
               "INNER JOIN playlist_song_join " +
               "ON song.id=playlist_song_join.songId " +
               "WHERE playlist_song_join.playlistId=:playlistId")
        List<Song> getSongsForPlaylist(final int playlistId);
    }