Definisci le relazioni tra gli oggetti

Poiché SQLite è un database relazionale, è possibile definire relazioni le entità. Ma mentre la maggior parte delle librerie di mappatura relazionali di oggetti consente oggetti si riferiscono l'uno all'altro, la stanza virtuale lo vieta esplicitamente. Per saperne di più sul ragionamento tecnico alla base di questa decisione, vedi Comprendere perché Room non Consenti riferimenti agli oggetti.

Due possibili approcci

In Stanza, ci sono due modi per definire ed eseguire query su una relazione tra entità: utilizzando una classe di dati intermedia con oggetti incorporati o un metodo di query relazionali con un ritorno multimap di testo.

Classe dati intermedia

Nell'approccio intermedio della classe di dati, definisci una classe di dati che modella relazione tra le entità Room. Questa classe di dati contiene gli accoppiamenti tra le istanze di un'entità e quelle di un'altra entità come incorporate oggetti. I tuoi metodi di query possono quindi restituire istanze di da utilizzare nell'app.

Ad esempio, puoi definire una classe di dati UserBook per rappresentare gli utenti della biblioteca con libri specifici pagati e definisci un metodo di query per recuperare un elenco di UserBook istanza dal database:

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

Tipi restituiti multimappa

Nell'approccio al tipo restituito da più mappe, non è necessario definire altre classi di dati. Definisci invece tipo restituito multimap per il metodo in base alla struttura della mappa desiderata e definire la relazione tra le entità direttamente nella query SQL.

Ad esempio, il seguente metodo di query restituisce una mappatura di User e Book per rappresentare gli utenti delle biblioteche con libri specifici già consultati:

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

Scegli un approccio

Room supporta entrambi gli approcci, quindi puoi usare qual è l'approccio migliore per la tua app. Questa sezione illustra alcuni dei motivi per cui potresti preferire l'uno o l'altro.

L'approccio intermedio della classe di dati consente di evitare di scrivere codice SQL complesso query, ma può anche comportare una maggiore complessità del codice a causa le ulteriori classi di dati richieste. In breve, il tipo restituito da multimap richiede che le query SQL eseguano un maggior lavoro, mentre i dati intermedi l'approccio didattico richiede che il tuo codice funzioni di più.

Se non hai un motivo specifico per utilizzare le classi di dati intermedie, consigliamo di utilizzare l'approccio basato sul tipo restituito multimap. Per scoprire di più su questo approccio, consulta Restituisci un multimappa.

Il resto di questa guida mostra come definire le relazioni utilizzando l'approccio con una classe di dati intermedia

Creazione di oggetti incorporati

A volte potresti voler esprimere un'entità o un oggetto dati come un tutto coeso nella logica del database, anche se l'oggetto contiene più campi. In queste situazioni, puoi utilizzare @Embedded per rappresentare un oggetto che desideri scomporre nel suo campi secondari all'interno di una tabella. Puoi quindi eseguire query sui campi incorporati seguendo la stessa procedura. per altre singole colonne.

Ad esempio, la classe User può includere un campo di tipo Address che rappresenta una composizione di campi denominati street, city, state e postCode, Per archiviare separatamente le colonne composte nella tabella, includi un elemento Campo Address nella classe User annotata con @Embedded, come mostrato nel seguente snippet di codice:

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

La tabella che rappresenta un oggetto User contiene quindi le colonne con quanto segue nomi: id, firstName, street, state, city e post_code.

Se un'entità ha più campi incorporati dello stesso tipo, puoi mantenerli di colonna univoca impostando la prefix proprietà. La stanza aggiunge quindi il valore fornito all'inizio di ogni colonna dell'oggetto incorporato.

Definizione di relazioni one-to-one

Una relazione uno a uno tra due entità è una relazione in cui ognuna dell'entità padre corrisponde esattamente a un'istanza dell'entità secondaria ed è vero anche il contrario.

Ad esempio, considera un'app di streaming musicale in cui l'utente ha una raccolta i brani di sua proprietà. Ogni utente ha una sola raccolta, corrisponde esattamente a un utente. Di conseguenza, esiste un relazione tra l'entità User e l'entità Library.

Per definire una relazione one-to-one, crea prima una classe per ognuno dei due le entità. Una delle entità deve Includere una variabile che faccia riferimento alla chiave primaria dell'altra entità.

Kotlin

@Entity
data class User(
    @PrimaryKey val userId: Long,
    val name: String,
    val age: Int
)

@Entity
data class Library(
    @PrimaryKey val libraryId: Long,
    val userOwnerId: Long
)

Java

@Entity
public class User {
    @PrimaryKey public long userId;
    public String name;
    public int age;
}

@Entity
public class Library {
    @PrimaryKey public long libraryId;
    public long userOwnerId;
}

Per eseguire query sull'elenco degli utenti e sulle librerie corrispondenti, devi prima modellare la relazione one-to-one tra le due entità. Per farlo, crea un una nuova classe di dati in cui ogni istanza contiene un'istanza dell'entità padre e all'istanza corrispondente dell'entità figlio. Aggiungi la @Relation sull'istanza dell'entità secondaria, con parentColumn impostato su il nome della colonna di chiave primaria dell'entità padre e entityColumn impostato sul nome della colonna dell'entità secondaria che fa riferimento all'entità padre chiave primaria dell'entità.

Kotlin

data class UserAndLibrary(
    @Embedded val user: User,
    @Relation(
         parentColumn = "userId",
         entityColumn = "userOwnerId"
    )
    val library: Library
)

Java

public class UserAndLibrary {
    @Embedded public User user;
    @Relation(
         parentColumn = "userId",
         entityColumn = "userOwnerId"
    )
    public Library library;
}

Infine, aggiungi alla classe DAO un metodo che restituisca tutte le istanze dei dati che accoppia l'entità padre e l'entità secondaria. Questo metodo richiede Spazio per eseguire due query, quindi aggiungi l'annotazione @Transaction a questo in modo che l'intera operazione venga eseguita a livello atomico.

Kotlin

@Transaction
@Query("SELECT * FROM User")
fun getUsersAndLibraries(): List<UserAndLibrary>

Java

@Transaction
@Query("SELECT * FROM User")
public List<UserAndLibrary> getUsersAndLibraries();

Definizione di relazioni one-to-many

Una relazione one-to-many tra due entità è una relazione in cui l'istanza dell'entità padre corrisponde a zero o più istanze dell'entità figlio dell'entità secondaria, ma ogni istanza dell'entità figlio può corrispondere esattamente a un dell'entità padre.

Nell'esempio dell'app di streaming musicale, supponiamo che l'utente abbia la possibilità di organizzare i brani in playlist. Ogni utente può creare tutte le playlist che vuole ma ogni playlist è creata da un solo utente. Pertanto, esiste una relazione one-to-many tra l'entità User e l'entità Playlist.

Per definire una relazione one-to-many, crea prima una classe per le due entità. Come in una relazione one-to-one, l'entità figlio deve includere una variabile che è un riferimento alla chiave primaria dell'entità padre.

Kotlin

@Entity
data class User(
    @PrimaryKey val userId: Long,
    val name: String,
    val age: Int
)

@Entity
data class Playlist(
    @PrimaryKey val playlistId: Long,
    val userCreatorId: Long,
    val playlistName: String
)

Java

@Entity
public class User {
    @PrimaryKey public long userId;
    public String name;
    public int age;
}

@Entity
public class Playlist {
    @PrimaryKey public long playlistId;
    public long userCreatorId;
    public String playlistName;
}

Per eseguire una query sull'elenco degli utenti e sulle playlist corrispondenti, devi prima modellare la relazione one-to-many tra le due entità. A questo scopo, crea una nuova classe di dati in cui ogni istanza contiene un'istanza dell'entità padre e un elenco di tutte le istanze dell'entità figlio corrispondenti. Aggiungi la @Relation sull'istanza dell'entità secondaria, con parentColumn impostato su il nome della colonna di chiave primaria dell'entità padre e entityColumn impostato sul nome della colonna dell'entità secondaria che fa riferimento all'entità padre chiave primaria dell'entità.

Kotlin

data class UserWithPlaylists(
    @Embedded val user: User,
    @Relation(
          parentColumn = "userId",
          entityColumn = "userCreatorId"
    )
    val playlists: List<Playlist>
)

Java

public class UserWithPlaylists {
    @Embedded public User user;
    @Relation(
         parentColumn = "userId",
         entityColumn = "userCreatorId"
    )
    public List<Playlist> playlists;
}

Infine, aggiungi alla classe DAO un metodo che restituisca tutte le istanze dei dati che accoppia l'entità padre e l'entità secondaria. Questo metodo richiede Spazio per eseguire due query, quindi aggiungi l'annotazione @Transaction a questo in modo che l'intera operazione venga eseguita a livello atomico.

Kotlin

@Transaction
@Query("SELECT * FROM User")
fun getUsersWithPlaylists(): List<UserWithPlaylists>

Java

@Transaction
@Query("SELECT * FROM User")
public List<UserWithPlaylists> getUsersWithPlaylists();

Definire relazioni di tipo multiple-to-many

Una relazione many-to-many tra due entità è una relazione in cui l'istanza dell'entità padre corrisponde a zero o più istanze dell'entità figlio ed è vero anche il contrario.

Nell'esempio dell'app di streaming musicale, considera i brani nelle playlist definite dall'utente. Ogni playlist può includere molti brani, ognuno dei quali può far parte di molti playlist diverse. Di conseguenza, esiste una relazione many-to-many tra l'entità Playlist e l'entità Song.

Per definire una relazione many-to-many, crea prima una classe per ciascuno dei due le entità. Relazioni many-to-many sono distinti dagli altri tipi di relazione perché in genere non c'è riferimento all'entità padre nell'entità secondaria. Crea invece un terzo per rappresentare un'entità associativa oppure eseguire un controllo incrociato tra le due entità. La tabella dei riferimenti incrociati deve avere colonne per chiave primaria di ciascuna entità nella relazione molti-a-molti rappresentata in nella tabella. In questo esempio, ogni riga della tabella dei riferimenti incrociati corrisponde a un accoppiamento di un'istanza Playlist e un'istanza Song in cui viene fatto riferimento sia incluso nella playlist indicata.

Kotlin

@Entity
data class Playlist(
    @PrimaryKey val playlistId: Long,
    val playlistName: String
)

@Entity
data class Song(
    @PrimaryKey val songId: Long,
    val songName: String,
    val artist: String
)

@Entity(primaryKeys = ["playlistId", "songId"])
data class PlaylistSongCrossRef(
    val playlistId: Long,
    val songId: Long
)

Java

@Entity
public class Playlist {
    @PrimaryKey public long playlistId;
    public String playlistName;
}

@Entity
public class Song {
    @PrimaryKey public long songId;
    public String songName;
    public String artist;
}

@Entity(primaryKeys = {"playlistId", "songId"})
public class PlaylistSongCrossRef {
    public long playlistId;
    public long songId;
}

Il passaggio successivo dipende da come vuoi eseguire la query su queste entità correlate.

  • Se vuoi eseguire una query sulle playlist e sull'elenco dei brani corrispondenti per ogni playlist, crea una nuova classe di dati contenente un singolo oggetto Playlist e un elenco di tutti gli oggetti Song inclusi nella playlist.
  • Se vuoi eseguire una query sui brani e su un elenco di playlist corrispondenti per ognuna, crea una nuova classe di dati contenente un singolo oggetto Song e un elenco di tutti gli oggetti Playlist in cui è incluso il brano.

In entrambi i casi, modella la relazione tra le entità utilizzando il metodo Proprietà associateBy nell'annotazione @Relation in ciascuno di queste classi per identificare l'entità di riferimento incrociato che fornisce la relazione tra l'entità Playlist e l'entità Song.

Kotlin

data class PlaylistWithSongs(
    @Embedded val playlist: Playlist,
    @Relation(
         parentColumn = "playlistId",
         entityColumn = "songId",
         associateBy = Junction(PlaylistSongCrossRef::class)
    )
    val songs: List<Song>
)

data class SongWithPlaylists(
    @Embedded val song: Song,
    @Relation(
         parentColumn = "songId",
         entityColumn = "playlistId",
         associateBy = Junction(PlaylistSongCrossRef::class)
    )
    val playlists: List<Playlist>
)

Java

public class PlaylistWithSongs {
    @Embedded public Playlist playlist;
    @Relation(
         parentColumn = "playlistId",
         entityColumn = "songId",
         associateBy = @Junction(PlaylistSongCrossref.class)
    )
    public List<Song> songs;
}

public class SongWithPlaylists {
    @Embedded public Song song;
    @Relation(
         parentColumn = "songId",
         entityColumn = "playlistId",
         associateBy = @Junction(PlaylistSongCrossref.class)
    )
    public List<Playlist> playlists;
}

Infine, aggiungi un metodo alla classe DAO per esporre la funzionalità di query le esigenze dell'app.

  • getPlaylistsWithSongs: questo metodo esegue query sul database e restituisce tutti gli oggetti PlaylistWithSongs risultanti.
  • getSongsWithPlaylists: questo metodo esegue query sul database e restituisce tutti gli oggetti SongWithPlaylists risultanti.

Ciascuno di questi metodi richiede Room per eseguire due query, quindi aggiungi @Transaction a entrambi i metodi in modo che l'intero eseguita a livello atomico.

Kotlin

@Transaction
@Query("SELECT * FROM Playlist")
fun getPlaylistsWithSongs(): List<PlaylistWithSongs>

@Transaction
@Query("SELECT * FROM Song")
fun getSongsWithPlaylists(): List<SongWithPlaylists>

Java

@Transaction
@Query("SELECT * FROM Playlist")
public List<PlaylistWithSongs> getPlaylistsWithSongs();

@Transaction
@Query("SELECT * FROM Song")
public List<SongWithPlaylists> getSongsWithPlaylists();

Definisci relazioni nidificate

A volte, potrebbe essere necessario eseguire query su un insieme di tre o più tabelle tutte correlate tra loro. In questo caso, definisci relazioni nidificate tra le tabelle.

Supponiamo che, nell'esempio dell'app di streaming musicale, tu voglia eseguire una query utenti, tutte le playlist di ciascun utente e tutti i brani contenuti in ogni playlist per ciascun utente. Gli utenti hanno un rapporto one-to-many con le playlist e le playlist hanno un rapporto many-to-many con canzoni. Il seguente esempio di codice mostra le classi che le rappresentano entità e la tabella di riferimenti incrociati per la relazione molti-a-molti tra playlist e brani:

Kotlin

@Entity
data class User(
    @PrimaryKey val userId: Long,
    val name: String,
    val age: Int
)

@Entity
data class Playlist(
    @PrimaryKey val playlistId: Long,
    val userCreatorId: Long,
    val playlistName: String
)

@Entity
data class Song(
    @PrimaryKey val songId: Long,
    val songName: String,
    val artist: String
)

@Entity(primaryKeys = ["playlistId", "songId"])
data class PlaylistSongCrossRef(
    val playlistId: Long,
    val songId: Long
)

Java

@Entity
public class User {
    @PrimaryKey public long userId;
    public String name;
    public int age;
}

@Entity
public class Playlist {
    @PrimaryKey public long playlistId;
    public long userCreatorId;
    public String playlistName;
}
@Entity
public class Song {
    @PrimaryKey public long songId;
    public String songName;
    public String artist;
}

@Entity(primaryKeys = {"playlistId", "songId"})
public class PlaylistSongCrossRef {
    public long playlistId;
    public long songId;
}

In primo luogo, modella la relazione tra due tabelle del set mentre come avviene normalmente, utilizzando una classe di dati @Relation. La l'esempio seguente mostra una classe PlaylistWithSongs che modella un modello many-to-many relazione tra la classe di entità Playlist e la classe di entità Song:

Kotlin

data class PlaylistWithSongs(
    @Embedded val playlist: Playlist,
    @Relation(
         parentColumn = "playlistId",
         entityColumn = "songId",
         associateBy = Junction(PlaylistSongCrossRef::class)
    )
    val songs: List<Song>
)

Java

public class PlaylistWithSongs {
    @Embedded public Playlist playlist;
    @Relation(
         parentColumn = "playlistId",
         entityColumn = "songId",
         associateBy = Junction(PlaylistSongCrossRef.class)
    )
    public List<Song> songs;
}

Dopo aver definito una classe di dati che rappresenta questa relazione, creane un'altra che modella la relazione tra un'altra tabella del set e la prima classe di relazione, "nidificare" la relazione esistente all'interno del nuovo uno. L'esempio seguente mostra una classe UserWithPlaylistsAndSongs che modella una relazione one-to-many tra la classe di entità User e la Classe di relazione PlaylistWithSongs:

Kotlin

data class UserWithPlaylistsAndSongs(
    @Embedded val user: User
    @Relation(
        entity = Playlist::class,
        parentColumn = "userId",
        entityColumn = "userCreatorId"
    )
    val playlists: List<PlaylistWithSongs>
)

Java

public class UserWithPlaylistsAndSongs {
    @Embedded public User user;
    @Relation(
        entity = Playlist.class,
        parentColumn = "userId",
        entityColumn = "userCreatorId"
    )
    public List<PlaylistWithSongs> playlists;
}

La classe UserWithPlaylistsAndSongs modella indirettamente le relazioni tra tutte e tre le classi di entità: User, Playlist e Song. Questo è illustrato nella Figura 1.

UserWithPlaylistAndSongs modella la relazione tra l&#39;utente e
  PlaylistWithSongs, che a sua volta modella la relazione tra Playlist
  e Song.

Figura 1. Diagramma delle classi delle relazioni app di streaming musicale.

Se il set contiene altre tabelle, crea una classe per modellare la relazione tra ogni tabella rimanente e la classe di relazione modellata le relazioni tra tutte le tabelle precedenti. Questo crea una catena di relazioni tra tutte le tabelle su cui vuoi eseguire la query.

Infine, aggiungi un metodo alla classe DAO per esporre la funzionalità di query che per le esigenze della tua app. Questo metodo richiede Room per eseguire più query, quindi aggiungi il token Annotazione @Transaction in modo che l'intera operazione venga eseguita a livello atomico:

Kotlin

@Transaction
@Query("SELECT * FROM User")
fun getUsersWithPlaylistsAndSongs(): List<UserWithPlaylistsAndSongs>

Java

@Transaction
@Query("SELECT * FROM User")
public List<UserWithPlaylistsAndSongs> getUsersWithPlaylistsAndSongs();

Risorse aggiuntive

Per saperne di più su come definire le relazioni tra le entità in Room, consulta dopo aver seguito le risorse aggiuntive.

Campioni

Video

Blog