Ponieważ SQLite jest relacyjną bazą danych, możesz definiować relacje między podmiotów. Chociaż większość bibliotek mapowania obiektowo-relacyjnej pozwala obiekty odwołują się do siebie, a pokój jawnie tego zabrania. Aby dowiedzieć się więcej o: uzasadnienie techniczne stojące za tą decyzją, można znaleźć w artykule Dlaczego pokój nie zezwalaj na odwołania do obiektów.
2 podejścia
W pokoju możesz definiować relacje między elementami i wysyłać dotyczące ich zapytania na 2 sposoby: przy użyciu pośredniej klasy danych z osadzone obiekty lub relacyjna metoda zapytania ze zwrotem wielomapowym typu.
Pośrednia klasa danych
W metodzie pośredniej klasy danych definiujesz klasę danych, która modeluje między elementami w pokoju. Ta klasa danych zawiera powiązania między wystąpieniami jednej jednostki a instancjami innej jednostki umieszczonej . Twoje metody zapytań mogą wtedy zwracać instancje tego klasa danych do wykorzystania w aplikacji.
Możesz np. zdefiniować klasę danych UserBook
reprezentującą użytkowników biblioteki
z konkretnymi książkami, a także zdefiniować metodę zapytania, która pozwoli pobrać listę
UserBook
instancja 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; }
Typy zwrotów w wielu mapach
W metodzie zwrotu dla wielu map nie trzeba określać żadnych dodatkowych klas danych. Zamiast tego definiujesz multimap zwracany przez wybrać odpowiednią metodę na podstawie żądanej struktury mapy oraz zdefiniować relacje między encjami bezpośrednio w zapytaniu SQL.
Na przykład ta metoda zapytania zwraca mapowanie User
i Book
wystąpienia reprezentujące użytkowników bibliotek z konkretnymi wystawionymi 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();
Wybór metody
Pokoje obsługują obie te metody, więc możesz która sprawdzi się najlepiej w przypadku Twojej aplikacji. W tej sekcji omówiono niektóre powodów, dla których być może wolisz którąś z tych opcji.
Metoda pośredniej klasy danych pozwala uniknąć pisania złożonego kodu SQL zapytań, ale może też powodować większą złożoność kodu ze względu na dodatkowych klas danych, których wymaga. Krótko mówiąc, zwracany typ wielu map wymaga większej pracy, a dane pośrednie – bardziej złożone wymaga zastosowania kodu.
Jeśli nie masz konkretnego powodu, aby używać pośrednich klas danych, zalecamy zastosowanie metody zwrotu z wielu map. Aby dowiedzieć się więcej o: w tym artykule znajdziesz informacje na temat zwracania multimapa.
W pozostałej części tego przewodnika pokazujemy, jak definiować relacje za pomocą pośredniej klasy danych.
Utwórz obiekty osadzone
Czasami chcesz wyrazić jednostkę lub obiekt danych jako
spójną całość w logice bazy danych, nawet jeśli obiekt zawiera
. W takiej sytuacji możesz użyć funkcji
@Embedded
adnotacji reprezentującej obiekt, który chcesz rozłożyć na
pól podrzędnych tabeli. Możesz teraz tworzyć zapytania dotyczące osadzonych pól, tak jak robisz to teraz
dla poszczególnych kolumn.
Na przykład klasa User
może zawierać pole typu Address
, które
przedstawia kompozycję pól o nazwach street
, city
, state
oraz
postCode
. Aby przechowywać utworzone kolumny oddzielnie w tabeli, dołącz atrybut
Pole Address
w klasie User
, które jest oznaczone adnotacją
@Embedded
, jako
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 wtedy kolumny z następującymi wartościami:
nazwy: id
, firstName
, street
, state
, city
i post_code
.
Jeśli element ma wiele osadzonych pól tego samego typu, możesz zachować każde z nich
unikalnej kolumny, ustawiając wartość
prefix
usłudze. Następnie pokój dodaje podaną wartość na początku każdej kolumny.
w umieszczonym obiekcie.
Zdefiniuj relacje 1:1
Relacja 1:1 między dwoma elementami to relacja, w której każdy wystąpienie elementu nadrzędnego odpowiada dokładnie jednemu wystąpieniu elementu podrzędnego podmiotu, i odwrotnie.
Weźmy na przykład aplikację do strumieniowego odtwarzania muzyki, w której użytkownik ma bibliotekę
własnych utworów. Każdy użytkownik ma tylko jedną bibliotekę, a każda z nich
odpowiada dokładnie jednemu użytkownikowi. W związku z tym mamy tu do czynienia
relacji między elementem User
a elementem Library
.
Aby zdefiniować relację 1:1, najpierw utwórz klasę dla każdej z nich podmiotów. Jeden z podmiotó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
modelowanie relacji jeden do jednego między tymi dwoma elementami. Aby to zrobić, utwórz
nowa klasa danych, w której każda instancja zawiera instancję encji nadrzędnej,
odpowiednie wystąpienie elementu podrzędnego. Dodaj @Relation
adnotacja do instancji elementu podrzędnego z wartością parentColumn
ustawioną na
nazwa kolumny klucza podstawowego jednostki nadrzędnej oraz entityColumn
ustaw na nazwę kolumny elementu podrzędnego, który odwołuje się do elementu nadrzędnego
klucz podstawowy jednostki.
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 wystąpienia danych
klasa łącząca encję nadrzędną z jednostką podrzędną. Ta metoda wymaga
Jest miejsce na 2 zapytania, więc dodaj do niego adnotację @Transaction
w taki sposób, aby całość przebiegała 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 dwoma elementami to relacja, w której każda z nich wystąpienie elementu nadrzędnego odpowiada 0 lub większej liczbie wystąpień elementu podrzędnego , ale każde wystąpienie elementu podrzędnego może odpowiadać tylko jednemu elementu nadrzędnego.
W przykładzie aplikacji do strumieniowego odtwarzania muzyki załóżmy, że użytkownik może porządkować treści
ze swoimi utworami w playlisty. Każdy użytkownik może utworzyć dowolną liczbę playlist,
ale każdą playlistę tworzy dokładnie jeden użytkownik. W związku z tym
relacji jeden do wielu między elementem User
a elementem Playlist
.
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ą, która jest 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 wysłać zapytanie o listę użytkowników i odpowiadające im playlisty, musisz najpierw
modelowanie relacji jeden do wielu między tymi dwoma elementami. Aby to zrobić, utwórz
nową klasę danych, w której każda instancja zawiera instancję encji nadrzędnej
listę wszystkich odpowiednich instancji encji podrzędnych. Dodaj @Relation
adnotacja do instancji elementu podrzędnego z wartością parentColumn
ustawioną na
nazwa kolumny klucza podstawowego jednostki nadrzędnej oraz entityColumn
ustaw na nazwę kolumny elementu podrzędnego, który odwołuje się do elementu nadrzędnego
klucz podstawowy jednostki.
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 wystąpienia danych
klasa łącząca encję nadrzędną z jednostką podrzędną. Ta metoda wymaga
Jest miejsce na 2 zapytania, więc dodaj do niego adnotację @Transaction
w taki sposób, aby całość przebiegała 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 dwoma elementami to relacja, w której każda z nich wystąpienie elementu nadrzędnego odpowiada 0 lub większej liczbie wystąpień elementu podrzędnego podmiotu, i odwrotnie.
W przykładzie w aplikacji do odtwarzania strumieniowego muzyki weź pod uwagę utwory znajdujące się na playlistach 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óżne playlisty. W ten sposób mamy do czynienia
między elementami Playlist
i Song
.
Aby zdefiniować relację wiele do wielu, najpierw utwórz klasę dla każdej z nich
podmiotów. Relacje wiele do wielu
różnią się od innych typów relacji, ponieważ zwykle nie ma
odwołanie do jednostki nadrzędnej w elemencie podrzędnym. Zamiast tego utwórz trzecią,
klasa do reprezentowania encji powiązanej lub danych wzajemnych
tabeli między tymi 2 elementami. Tabela z odnośnikami musi zawierać kolumny dla
klucz podstawowy z każdej jednostki w relacji wiele do wielu reprezentowanych w
tabeli. W tym przykładzie każdy wiersz w tabeli odniesień odpowiada
parowanie instancji Playlist
z instancją Song
, gdzie odwołuje się
utwór znajduje się na wskazanej 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 do tych powiązanych elementów.
- Jeśli chcesz wysłać zapytanie dotyczące playlist i listy odpowiadających im utworów,
dla każdej playlisty utwórz nową klasę danych zawierającą pojedynczy obiekt
Playlist
. i listę wszystkich obiektówSong
zawartych na playliście. - Jeśli chcesz wyświetlić zapytanie o utwory i listę odpowiednich playlist:
utwórz nową klasę danych zawierającą pojedynczy obiekt
Song
oraz listę wszystkich obiektówPlaylist
, w których występuje utwór.
W obu przypadkach modeluj relacje między elementami przy użyciu
associateBy
w adnotacji @Relation
w każdym z
tych klas do identyfikacji elementu odsyłającego stanowiącego relację
między elementami Playlist
i 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ć funkcję zapytań do potrzeb aplikacji.
getPlaylistsWithSongs
: ta metoda wysyła zapytania do bazy danych i zwraca wszystkie otrzymane obiektyPlaylistWithSongs
.getSongsWithPlaylists
: ta metoda wysyła zapytania do bazy danych i zwraca wszystkie otrzymane obiektySongWithPlaylists
.
Każda z tych metod wymaga, by w pomieszczeniu były wykonywane 2 zapytania, więc dodaj metodę
@Transaction
do obu metod, aby cały
jest przeprowadzana 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
Czasami konieczne może być wysłanie zapytania obejmującego co najmniej 3 tabele, które zawierają wszystkie powiązane ze sobą. W takim przypadku definiujesz relacje zagnieżdżone. między tabelami.
Załóżmy, że w przykładzie aplikacji do strumieniowego odtwarzania muzyki chcesz przesłać zapytanie do wszystkich użytkowników, wszystkie playlisty każdego użytkownika i wszystkie utwory na każdej playliście dla każdego użytkownika. Użytkownicy mają relację jeden do wielu z playlisty i playlisty są powiązane relacją wiele do wielu utworów. Poniższy przykładowy kod pokazuje klasy, które je reprezentują encje oraz tabelę odniesień do relacji wiele do wielu. między playlistami i 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 modeluj zależność między 2 tabelami w zestawie
za pomocą klasy danych,
@Relation
.
przykład poniżej pokazuje klasę PlaylistWithSongs
, która modeluje modelowanie wiele do wielu
relacja 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ą
klasa danych, która modeluje relację między inną tabelą z Twojego zbioru a
pierwszą klasę relacji („zagnieżdżanie”), istniejących relacji w nowym
jeden. Poniższy przykład pokazuje klasę UserWithPlaylistsAndSongs
, która modeluje
relacji jeden do wielu między klasą encji User
a funkcją
Klasa 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
. To jest
przedstawione na rys. 1.
![UserWithplaylistAndSongs modeluje relację między Użytkownikiem a
PlaylistWithSongs, która modeluje relacje między playlistą
i Utwór.](https://developer.android.com/static/images/training/data-storage/room_nested_relationships.png?authuser=19&hl=pl)
Rysunek 1. Schemat klas relacji w Przykład aplikacji do strumieniowego odtwarzania muzyki.
Jeśli w zestawie znajduje się więcej tabel, utwórz klasę do modelowania relacji między każdą pozostałą tabelą a klasą relacji, która modeluje zależności między wszystkimi poprzednimi tabelami. Powoduje to utworzenie łańcucha zagnieżdżonych między wszystkimi tabelami, których ma dotyczyć zapytanie.
Na koniec dodaj metodę do klasy DAO, aby udostępnić funkcję zapytań, która
do aplikacji. Ta metoda wymaga, by usługa Room mogła uruchamiać wiele zapytań, więc dodaj metodę
Adnotacja @Transaction
w taki sposób, aby całość przebiegała atomowo:
Kotlin
@Transaction @Query("SELECT * FROM User") fun getUsersWithPlaylistsAndSongs(): List<UserWithPlaylistsAndSongs>
Java
@Transaction @Query("SELECT * FROM User") public List<UserWithPlaylistsAndSongs> getUsersWithPlaylistsAndSongs();
Dodatkowe materiały
Więcej informacji o definiowaniu relacji między elementami w pokoju znajdziesz w z poniższych dodatkowych materiałów.
Próbki
Filmy
- Co nowego w Pokojach (Android dla programistów Zjazd 2019)