SQLite to relacyjna baza danych, więc możesz definiować relacje między encjami. Jednak większość bibliotek mapowania obiektów i relacji pozwala na odniesienie się do siebie obiektów encji, jednak funkcja Room wyraźnie zabrania tego. Aby poznać uzasadnienie techniczne tej decyzji, przeczytaj artykuł Dlaczego sala nie zezwala na odwołania do obiektów.
Dwa możliwe podejścia
W przypadku sali można na 2 sposoby definiować relacje między encjami i wysyłać dotyczące ich zapytania: przy użyciu pośredniej klasy danych z umieszczonymi obiektami lub relacyjnej metody zapytania ze zwracanym typem wielu map.
Pośrednia klasa danych
W metodzie pośredniej klasy danych definiujesz klasę danych, która modeluje relację między encjami dotyczącymi sal. Ta klasa danych łączy wystąpienia jednej encji z instancjami innej jako osadzone obiekty. Twoje metody zapytań będą mogły zwracać wystąpienia tej klasy danych do użycia w aplikacji.
Możesz np. zdefiniować klasę danych UserBook
, która będzie reprezentować użytkowników biblioteki z wyrejestrowanymi książkami, oraz metodę zapytania, która pobierze listę instancji UserBook
z bazy danych:
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; }
Zwracane typy wielu map
W metodzie zwracanego typu wielu map nie musisz określać żadnych dodatkowych klas danych. Zamiast tego definiujesz typ zwrotu multimap dla metody na podstawie żądanej struktury mapy i definiujesz relację między encjami bezpośrednio w zapytaniu SQL.
Na przykład ta metoda zapytania zwraca mapowanie wystąpień User
i Book
, które reprezentują użytkowników biblioteki, którzy wymeldowali się z konkretnymi książkami:
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();
Wybierz metodę
Usługa Room obsługuje oba te podejścia, więc możesz wybrać tę, która sprawdzi się najlepiej w przypadku Twojej aplikacji. W tej sekcji omawiamy powody, dla których możesz używać jednego z nich.
Podejście średniowiecznej klasy danych pozwala uniknąć pisania skomplikowanych zapytań SQL, ale może też zwiększyć złożoność kodu ze względu na wymagane dodatkowe klasy danych. Krótko mówiąc, metoda zwrotu z wieloma mapami wymaga więcej zapytań SQL, a metoda pośredniej klasy danych – więcej pracy z kodem.
Jeśli nie masz konkretnego powodu, aby używać pośrednich klas danych, zalecamy użycie metody zwracania z użyciem wielu map. Więcej informacji o tym podejściu znajdziesz w artykule o zwracaniu mapy wielomapowej.
W pozostałej części tego przewodnika dowiesz się, jak definiować relacje za pomocą metody pośredniej klas danych.
Tworzenie obiektów umieszczonych
Czasami chcesz wyrazić encję lub obiekt danych jako spójną całość w ramach logiki bazy danych, nawet jeśli obiekt zawiera kilka pól. W takich sytuacjach możesz użyć adnotacji @Embedded
do reprezentowania obiektu, który chcesz rozdzielić na pola podrzędne w tabeli. Możesz potem wysyłać zapytania o umieszczone pola, tak jak w przypadku innych kolumn.
Na przykład klasa User
może zawierać pole typu Address
, które reprezentuje kompozycję pól o nazwach street
, city
, state
i postCode
. Aby osobno przechowywać w tabeli utworzone kolumny, dodaj do klasy User
pole Address
z adnotacją @Embedded
, jak pokazano w tym fragmencie kodu:
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; }
Tabela reprezentująca obiekt User
zawiera kolumny o tych nazwach: id
, firstName
, street
, state
, city
i post_code
.
Jeśli w elemencie jest wiele umieszczonych pól tego samego typu, możesz zadbać o niepowtarzalność każdej kolumny, ustawiając właściwość prefix
. Następnie dodaje podaną wartość na początku nazwy każdej kolumny w umieszczonym obiekcie.
Zdefiniuj relacje jeden do jednego
Relacja 1 do 1 między 2 elementami to relacja, w której każde wystąpienie encji nadrzędnej odpowiada dokładnie 1 wystąpieniu jednostki podrzędnej i odwrotnie.
Weźmy na przykład aplikację do strumieniowego odtwarzania muzyki, w której użytkownik ma bibliotekę swoich utworów. Każdy użytkownik ma tylko jedną bibliotekę, a każda z nich odpowiada dokładnie jednemu użytkownikowi. Dlatego między elementem User
a elementem Library
występuje relacja 1:1.
Aby zdefiniować relację jeden do jednego, najpierw utwórz klasę dla każdej z 2 elementów. Jeden z elementów musi zawierać zmienną, która jest odwołaniem do klucza podstawowego innego elementu.
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; }
Aby wysłać zapytanie o listę użytkowników i odpowiadających im bibliotek, musisz najpierw wymodelować relację jeden do jednego między 2 encjami. Aby to zrobić, utwórz nową klasę danych, w której każda instancja będzie zawierać instancję jednostki nadrzędnej i odpowiednią instancję jednostki podrzędnej. Do instancji encji podrzędnej dodaj adnotację @Relation
, w której wartość parentColumn
będzie odpowiadać nazwie kolumny klucza podstawowego elementu nadrzędnego, a entityColumn
– nazwą kolumny jednostki podrzędnej, która odwołuje się do klucza podstawowego elementu nadrzędnego.
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; }
Na koniec dodaj do klasy DAO metodę, która zwraca wszystkie instancje klasy danych łączącej encję nadrzędną z podrzędną. Ta metoda wymaga wykonania 2 zapytań w ramach usługi, więc dodaj do niej adnotację @Transaction
, aby cała operacja została przeprowadzona atomowo.
Kotlin
@Transaction @Query("SELECT * FROM User") fun getUsersAndLibraries(): List<UserAndLibrary>
Java
@Transaction @Query("SELECT * FROM User") public List<UserAndLibrary> getUsersAndLibraries();
Zdefiniuj relacje jeden do wielu
Relacja jeden do wielu między 2 elementami to relacja, w której każde wystąpienie encji nadrzędnej odpowiada 0 lub większej liczbie wystąpień jednostki podrzędnej, ale każde wystąpienie encji podrzędnej może odpowiadać tylko jednemu wystąpieniem elementu nadrzędnego.
Załóżmy, że w przypadku aplikacji do strumieniowego odtwarzania muzyki użytkownik może uporządkować utwory w playlisty. Każdy użytkownik może utworzyć dowolną liczbę playlist,
ale każda z nich jest tworzona przez dokładnie jednego użytkownika. Dlatego między elementem User
a elementem Playlist
występuje relacja jeden do wielu.
Aby zdefiniować relację jeden do wielu, najpierw utwórz klasę dla tych dwóch encji. Tak jak w relacji jeden do jednego, encja podrzędna musi zawierać zmienną będącą odwołaniem do klucza podstawowego jednostki nadrzędnej.
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; }
Aby przesłać zapytanie dotyczące listy użytkowników i odpowiadających im playlist, musisz najpierw wymodelować relację jeden do wielu między 2 elementami. W tym celu utwórz nową klasę danych, w której każda instancja będzie zawierać instancję encji nadrzędnej oraz listę wszystkich odpowiadających jej instancji podrzędnych. Do instancji encji podrzędnej dodaj adnotację @Relation
, w której wartość parentColumn
będzie odpowiadać nazwie kolumny klucza podstawowego elementu nadrzędnego, a entityColumn
– nazwą kolumny jednostki podrzędnej, która odwołuje się do klucza podstawowego elementu nadrzędnego.
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; }
Na koniec dodaj do klasy DAO metodę, która zwraca wszystkie instancje klasy danych łączącej encję nadrzędną z podrzędną. Ta metoda wymaga wykonania 2 zapytań w ramach usługi, więc dodaj do niej adnotację @Transaction
, aby cała operacja została przeprowadzona atomowo.
Kotlin
@Transaction @Query("SELECT * FROM User") fun getUsersWithPlaylists(): List<UserWithPlaylists>
Java
@Transaction @Query("SELECT * FROM User") public List<UserWithPlaylists> getUsersWithPlaylists();
Zdefiniuj relacje wiele do wielu
Relacja wiele do wielu między 2 elementami to relacja, w której każde wystąpienie encji nadrzędnej odpowiada 0 lub większej liczbie wystąpień jednostki podrzędnej, a odwrotność jest również rzeczywista.
W przykładzie aplikacji do strumieniowego odtwarzania muzyki przyjrzyjmy się utworom z playlist zdefiniowanych przez użytkownika.
Każda playlista może zawierać wiele utworów, a każdy z nich może być częścią wielu różnych playlist. Dlatego między elementem Playlist
a elementem Song
istnieje relacja wiele do wielu.
Aby zdefiniować relację wiele do wielu, najpierw utwórz klasę dla każdej z 2 elementów. Relacje wiele do wielu różnią się od innych typów relacji, ponieważ zasadniczo nie zawierają odniesienia do encji nadrzędnej. Zamiast tego utwórz trzecią klasę, która będzie reprezentować jednostkę powiązaną lub tabelę porównawczą między tymi 2 elementami. Tabela odniesień musi zawierać kolumny klucza podstawowego z każdego elementu w relacji wiele do wielu reprezentowanych w tabeli. W tym przykładzie każdy wiersz w tabeli referencyjnej odpowiada parze wystąpienia Playlist
i instancji Song
, gdzie wskazany utwór znajduje się na wymienionej playliście.
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; }
Następny krok zależy od tego, jak chcesz wysyłać zapytania dotyczące tych powiązanych elementów.
- Jeśli chcesz utworzyć zapytanie o playlisty i listę odpowiednich utworów na potrzeby każdej playlisty, utwórz nową klasę danych zawierającą pojedynczy obiekt
Playlist
i listę wszystkich obiektówSong
znajdujących się na playliście. - Jeśli chcesz wysłać zapytania dotyczące utworów i listy odpowiadających im playlist, utwórz nową klasę danych zawierającą pojedynczy obiekt
Song
i listę wszystkich obiektówPlaylist
, w których znajduje się dany utwór.
W obu przypadkach modeluj relację między encjami, korzystając z właściwości associateBy
w adnotacji @Relation
w każdej z tych klas, aby zidentyfikować encję referencyjną będącą źródłem relacji między elementem Playlist
a encją 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; }
Na koniec dodaj metodę do klasy DAO, aby udostępnić funkcje zapytań, których potrzebuje Twoja aplikacja.
getPlaylistsWithSongs
: ta metoda wysyła zapytanie do bazy danych i zwraca wszystkie powstałe w ten sposób obiektyPlaylistWithSongs
.getSongsWithPlaylists
: ta metoda wysyła zapytanie do bazy danych i zwraca wszystkie powstałe w ten sposób obiektySongWithPlaylists
.
Każda z tych metod wymaga wykonania 2 zapytań w pomieszczeniu, więc dodaj do obu metod adnotację @Transaction
, aby cała operacja była wykonywana atomowo.
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();
Zdefiniuj relacje zagnieżdżone
Czasem możesz chcieć wysłać zapytanie do zestawu 3 lub większej liczby tabel, które są ze sobą powiązane. W takim przypadku definiujesz relacje zagnieżdżone między tabelami.
Załóżmy, że w przypadku aplikacji do strumieniowego odtwarzania muzyki chcesz wysłać zapytanie do wszystkich użytkowników, wszystkich playlist każdego użytkownika oraz wszystkich utworów na każdej playliście w przypadku każdego użytkownika. Użytkownicy korzystają z playlist w relacji jeden do wielu, a playlisty – wiele do wielu z utworami. Poniższy przykładowy kod pokazuje klasy reprezentujące te elementy, a także tabelę odsyłającą dla relacji wiele do wielu między playlistami a utworami:
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; }
Najpierw w zwykły sposób wymodeluj relację między 2 tabelami w zbiorze, korzystając z klasy danych i adnotacji @Relation
. Poniższy przykład pokazuje klasę PlaylistWithSongs
, która modeluje relację wiele do wielu między klasą encji Playlist
a klasą encji 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; }
Po zdefiniowaniu klasy danych, która reprezentuje tę relację, utwórz kolejną klasę danych, która modeluje relację między inną tabelą z Twojego zbioru a pierwszą klasą relacji, „zagnieżdżając” istniejącą relację w nowej. Poniższy przykład pokazuje klasę UserWithPlaylistsAndSongs
, która modeluje relację jeden do wielu między klasą encji User
i klasą relacji 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; }
Klasa UserWithPlaylistsAndSongs
pośrednio modeluje relacje między wszystkimi 3 klasami encji: User
, Playlist
i Song
. Zostało to pokazane na ilustracji 1.
Jeśli w zestawie jest więcej tabel, utwórz klasę do modelowania zależności między wszystkimi pozostałymi tabelami a klasą relacji, która modeluje relacje między wszystkimi poprzednimi tabelami. Spowoduje to utworzenie łańcucha zagnieżdżonych relacji między wszystkimi tabelami, których ma dotyczyć zapytanie.
Na koniec dodaj metodę do klasy DAO, aby udostępnić funkcję zapytań, której potrzebuje Twoja aplikacja. Ta metoda wymaga wykonania wielu zapytań w komponencie, więc dodaj adnotację @Transaction
, aby cała operacja została przeprowadzona atomowo:
Kotlin
@Transaction @Query("SELECT * FROM User") fun getUsersWithPlaylistsAndSongs(): List<UserWithPlaylistsAndSongs>
Java
@Transaction @Query("SELECT * FROM User") public List<UserWithPlaylistsAndSongs> getUsersWithPlaylistsAndSongs();
Dodatkowe materiały
Aby dowiedzieć się więcej o definiowaniu relacji między elementami w salach, zapoznaj się z poniższymi dodatkowymi zasobami.
Próbki
Filmy
- Co nowego w pokoju (Summit Android 2019)