SQLite to relacyjna baza danych, dlatego możesz definiować relacje między encjami. Większość bibliotek mapowania obiektowo-relacyjnego umożliwia obiektom encji odwoływanie się do siebie, ale Room wyraźnie tego zabrania. Aby dowiedzieć się więcej o technicznych przyczynach tej decyzji, przeczytaj artykuł Dlaczego Room nie zezwala na odwołania do obiektów.
Typy relacji
Room obsługuje te typy relacji:
- Jeden do jednego: relacja, w której jedna encja jest powiązana z inną encją.
- Jeden do wielu: relacja, w której jedna encja może być powiązana z wieloma encjami innego typu.
- Wiele do wielu: relacja, w której wiele encji jednego typu może być powiązanych z wieloma encjami innego typu. Zwykle wymaga to tabeli łączącej.
- Relacje zagnieżdżone z użyciem obiektów osadzonych: relacja, w której encja zawiera inną encję jako właściwość, a ta zagnieżdżona encja może zawierać inne encje. Używa adnotacji
@Embedded.
Wybór między 2 podejściami
W Room relację między encjami można zdefiniować i wysłać do niej zapytanie na 2 sposoby. Możesz użyć:
- pośredniej klasy danych z obiektami osadzonymi lub
- funkcji zapytań relacyjnych z typem zwracanym multimap.
Jeśli nie masz konkretnego powodu, aby używać pośrednich klas danych, zalecamy używanie typu zwracanego multimap. Więcej informacji o tym podejściu znajdziesz w artykule Zwracanie multimapy.
Podejście z użyciem pośredniej klasy danych pozwala uniknąć pisania złożonych zapytań SQL, ale może też zwiększyć złożoność kodu, ponieważ wymaga dodatkowych klas danych. Krótko mówiąc, podejście z użyciem typu zwracanego multimap wymaga, aby zapytania SQL wykonywały więcej pracy, a podejście z użyciem pośredniej klasy danych wymaga, aby więcej pracy wykonywał kod.
Używanie podejścia z użyciem pośredniej klasy danych
W podejściu z użyciem pośredniej klasy danych definiujesz klasę danych, która modeluje relację między encjami Room. Ta klasa danych zawiera pary instancji jednej encji i instancji innej encji jako osadzone obiekty. Funkcje zapytań mogą następnie zwracać instancje tej klasy danych do użycia w aplikacji.
Możesz na przykład zdefiniować klasę danych UserBook, która będzie reprezentować użytkowników biblioteki z wypożyczonymi książkami, oraz funkcję zapytań, która będzie pobierać z bazy danych listę instancji UserBook:
@Dao interface UserBookDao { @Query( """ SELECT user.name AS userName, book.name AS bookName FROM user JOIN book ON user.id = book.user_id """ ) fun loadUserAndBookNames(): LiveData<List<UserBook>> } data class UserBook(val userName: String, val bookName: String)
Używanie podejścia z użyciem typu zwracanego multimap
W podejściu z użyciem typu zwracanego multimap nie musisz definiować żadnych dodatkowych klas danych. Zamiast tego definiujesz typ zwracany multimap dla funkcji na podstawie żądanej struktury mapy i definiujesz relację między encjami bezpośrednio w zapytaniu SQL.
Na przykład ta funkcja zapytań zwraca mapowanie instancji User i Book, które reprezentuje użytkowników biblioteki z wypożyczonymi książkami:
@Query( """ SELECT * FROM user JOIN book ON user.id = book.user_id """ ) suspend fun loadUserAndBookNames(): Map<User, List<Book>>
W przypadku typów zwracanych multimap możesz też wysyłać zapytania o relacje jeden do jednego, które nie obejmują innej encji. Ta funkcja zapytań zwraca mapowanie
User i liczby wypożyczonych przez niego książek za pomocą
@MapColumn adnotacji:
@Query( """ SELECT user.*, COUNT(book.id) AS book_count FROM user LEFT JOIN book ON user.id = book.user_id GROUP BY user.id """ ) suspend fun loadUserAndBookCount(): Map<User, @MapColumn(columnName = "book_count") Int>
Tworzenie obiektów osadzonych
Czasami chcesz wyrazić encję lub obiekt danych jako spójną całość w logice bazy danych, nawet jeśli obiekt zawiera kilka właściwości. W takich
sytuacjach użyj adnotacji @Embedded, aby rozłożyć obiekt na
jego podwłaściwości w tabeli. Następnie możesz wysyłać zapytania o właściwości osadzone tak samo jak w przypadku innych kolumn.
Na przykład klasa User może zawierać właściwość Address, która reprezentuje kompozycję właściwości street, city, state i postCode. Aby przechowywać skomponowane kolumny oddzielnie w tabeli, dodaj adnotację @Embedded do właściwości Address w klasie User. Ten fragment kodu pokazuje tę konfigurację:
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? )
Tabela reprezentująca obiekt User zawiera wtedy kolumny o tych nazwach: id, firstName, street, state, city i post_code.
Jeśli encja ma kilka właściwości osadzonych tego samego typu, możesz zachować
unikalność każdej kolumny, ustawiając właściwość prefix. Room doda wtedy podaną wartość na początku każdej nazwy kolumny w obiekcie osadzonym.