Wybieranie typów relacji między obiektami

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.