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.

Rodzaje relacji

Room obsługuje te rodzaje 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 pole, a ta zagnieżdżona encja może zawierać kolejne encje. Używana jest tu adnotacja @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
  • relacyjnej metody zapytań 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 pośrednią klasą 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 typem zwracanym multimap wymaga, aby zapytania SQL wykonywały więcej pracy, a podejście z pośrednią klasą danych wymaga, aby więcej pracy wykonywał kod.

Używanie podejścia z pośrednią klasą danych

W podejściu z pośrednią klasą 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. Metody 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 metodę zapytań, która będzie pobierać z bazy danych listę instancji UserBook:

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;
}

Używanie podejścia z typami zwracanymi multimap

W podejściu z typem zwracanym multimap nie musisz definiować żadnych dodatkowych klas danych. Zamiast tego definiujesz typ zwracany multimap dla metody na podstawie struktury mapy, którą chcesz uzyskać, i definiujesz relację między encjami bezpośrednio w zapytaniu SQL.

Na przykład ta metoda zapytań zwraca mapowanie instancji User i Book, które reprezentują użytkowników biblioteki z wypożyczonymi 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();

Tworzenie obiektów osadzonych

Czasami w logice bazy danych chcesz przedstawić encję lub obiekt danych jako spójną całość, nawet jeśli obiekt zawiera kilka pól. W tych sytuacjach możesz użyć adnotacji @Embedded, aby przedstawić obiekt , który chcesz rozłożyć na jego podpola w tabeli. Następnie możesz wysyłać zapytania do pól osadzonych tak samo jak do innych pojedynczych 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 przechowywać kolumny składowe oddzielnie w tabeli, dodaj pole Address. Powinno ono występować w klasie User z adnotacją @Embedded. Poniższy fragment kodu pokazuje, jak to zrobić:

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 o tych nazwach: id, firstName, street, state, city i post_code.

Jeśli encja ma kilka pól 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 nazwy każdej kolumny w obiekcie osadzonym.

Dodatkowe materiały

Więcej informacji o definiowaniu relacji między encjami w Room znajdziesz w tych materiałach.

Filmy

Blogi