Da SQLite eine relationale Datenbank ist, können Sie Beziehungen zwischen Entitäten definieren. Während die meisten ORM-Bibliotheken (Object-Relational Mapping) es ermöglichen, dass sich Entity-Objekte gegenseitig referenzieren, ist dies in Room ausdrücklich verboten. Die technischen Gründe für diese Entscheidung finden Sie unter Warum sind Objektverweise in Room nicht zulässig?.
Arten von Beziehungen
Room unterstützt die folgenden Beziehungstypen:
- 1:1: Stellt eine Beziehung dar, in der eine einzelne Entität mit einer anderen einzelnen Entität in Beziehung steht.
- 1:n: Stellt eine Beziehung dar, in der eine einzelne Entität mit mehreren Entitäten eines anderen Typs verknüpft werden kann.
- Viele-zu-viele: Stellt eine Beziehung dar, in der mehrere Entitäten eines Typs mit mehreren Entitäten eines anderen Typs verknüpft werden können. Dazu ist in der Regel eine Verbindungstabelle erforderlich.
- Geschachtelte Beziehungen (mit eingebetteten Objekten): Stellt eine Beziehung dar, in der eine Entität eine andere Entität als Feld enthält und diese geschachtelte Entität weitere Entitäten enthalten kann. Dazu wird die Annotation
@Embeddedverwendet.
Zwischen zwei Ansätzen wählen
In Room gibt es zwei Möglichkeiten, eine Beziehung zwischen Entitäten zu definieren und abzufragen. Sie haben folgende Möglichkeiten:
- Eine Zwischenklasse mit eingebetteten Objekten oder
- Eine relationale Abfragemethode mit einem Multimap-Rückgabetyp.
Wenn Sie keinen bestimmten Grund für die Verwendung von Zwischen-Datenklassen haben, empfehlen wir die Verwendung des Multimap-Rückgabetyps. Weitere Informationen zu diesem Ansatz finden Sie unter Multimap zurückgeben.
Mit dem Ansatz für Zwischen-Datenklassen können Sie komplexe SQL-Abfragen vermeiden. Er kann aber auch zu einer erhöhten Codekomplexität führen, da zusätzliche Datenklassen erforderlich sind. Kurz gesagt: Beim Ansatz mit dem Multimap-Rückgabetyp müssen Ihre SQL-Abfragen mehr leisten, beim Ansatz mit der Zwischenklasse müssen Sie mehr Code schreiben.
Zwischendatenklasse verwenden
Bei diesem Ansatz definieren Sie eine Datenklasse, die die Beziehung zwischen Ihren Room-Entitäten modelliert. Diese Datenklasse enthält die Paare zwischen Instanzen einer Entität und Instanzen einer anderen Entität als eingebettete Objekte. Ihre Abfragemethoden können dann Instanzen dieser Datenklasse zur Verwendung in Ihrer App zurückgeben.
Sie können beispielsweise eine UserBook-Datenklasse definieren, um Bibliotheksnutzer mit bestimmten ausgeliehenen Büchern darzustellen, und eine Abfragemethode definieren, um eine Liste von UserBook-Instanzen aus der Datenbank abzurufen:
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;
}
Multimap-Rückgabetypen verwenden
Bei diesem Ansatz mit Rückgabetyp müssen Sie keine zusätzlichen Datenklassen definieren. Stattdessen definieren Sie einen Multimap-Rückgabetyp für Ihre Methode basierend auf der gewünschten Kartenstruktur und definieren die Beziehung zwischen Ihren Einheiten direkt in Ihrer SQL-Abfrage.
Die folgende Abfragemethode gibt beispielsweise eine Zuordnung von User- und Book-Instanzen zurück, um Bibliotheksnutzer mit bestimmten ausgeliehenen Büchern darzustellen:
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();
Eingebettete Objekte erstellen
Manchmal möchten Sie eine Entität oder ein Datenobjekt in Ihrer Datenbanklogik als zusammenhängendes Ganzes darstellen, auch wenn das Objekt mehrere Felder enthält. In diesen Fällen können Sie die Annotation @Embedded verwenden, um ein Objekt darzustellen, das Sie in einer Tabelle in seine Unterfelder zerlegen möchten. Sie können dann die eingebetteten Felder genauso abfragen wie andere einzelne Spalten.
Ihre Klasse User kann beispielsweise ein Feld vom Typ Address enthalten, das eine Zusammensetzung von Feldern mit den Namen street, city, state und postCode darstellt. Wenn Sie die zusammengesetzten Spalten separat in der Tabelle speichern möchten, fügen Sie das Feld Address ein. Das sollte in der Klasse User mit der Annotation @Embedded angezeigt werden. Das folgende Code-Snippet veranschaulicht dies:
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;
}
Die Tabelle, die ein User-Objekt darstellt, enthält dann Spalten mit den folgenden Namen: id, firstName, street, state, city und post_code.
Wenn eine Entität mehrere eingebettete Felder desselben Typs hat, können Sie jede Spalte eindeutig halten, indem Sie die Eigenschaft prefix festlegen. Room fügt den angegebenen Wert dann am Anfang jedes Spaltennamens im eingebetteten Objekt hinzu.
Zusätzliche Ressourcen
Weitere Informationen zum Definieren von Beziehungen zwischen Entitäten in Room finden Sie in den folgenden zusätzlichen Ressourcen.
Videos
- What's New in Room (Android Dev Summit 2019)