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
- Co nowego w Room (Android Dev Summit '19)