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 oggettiSong
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 oggettiPlaylist
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 oggettiPlaylistWithSongs
risultanti.getSongsWithPlaylists
: questo metodo esegue query sul database e restituisce tutti gli oggettiSongWithPlaylists
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.
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
- Novità nelle stanze (Sviluppo Android Summit '19)