Zdefiniuj relacje między obiektami

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ów Song 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ów Playlist, 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 obiekty PlaylistWithSongs.
  • getSongsWithPlaylists: ta metoda wysyła zapytania do bazy danych i zwraca wszystkie otrzymane obiekty SongWithPlaylists.

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.

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

Blogi