Beziehungen zwischen Objekten definieren

Da SQLite eine relationale Datenbank ist, können Sie Beziehungen zwischen Entitäten. Während die meisten objektrelationalen Mapping-Bibliotheken -Objekte verweisen aufeinander, wird von Room ausdrücklich untersagt. Weitere Informationen zu der technischen Begründung für diese Entscheidung unter Warum Objektverweise zulassen.

Zwei mögliche Ansätze

In Room gibt es zwei Möglichkeiten, eine Beziehung zwischen Entitäten zu definieren und abzufragen: mit einer Zwischendatenklasse mit eingebettete Objekte oder eine relationale Abfragemethode mit Multimap-Rückgabe Typ.

Datenmittelklasse

Bei dieser Methode definieren Sie eine Datenklasse, die die Beziehung zwischen den Raumentitäten. Diese Datenklasse enthält die Paare zwischen Instanzen einer Entität und Instanzen einer anderen Entität als eingebettet Objekte. Ihre Abfragemethoden können dann Instanzen dieses Datenklasse für die Verwendung in Ihrer App.

Sie können beispielsweise eine UserBook-Datenklasse definieren, um Bibliotheksnutzer zu repräsentieren. die bestimmte Bücher ausgecheckt haben, und definieren Sie eine Abfragemethode, um eine Liste UserBook Instanzen aus der Datenbank:

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

Rückgabetypen für mehrere Karten

Beim Ansatz des Multimap-Rückgabetyps müssen Sie keine zusätzlichen Datenklassen verwendet werden. Stattdessen definieren Sie eine multimap-Rückgabetyp für Ihre -Methode basierend auf der gewünschten Kartenstruktur und definieren Sie die Beziehung zwischen den Entitäten direkt in der SQL-Abfrage.

Die folgende Abfragemethode gibt beispielsweise eine Zuordnung von User und Book zurück. -Instanzen zur Darstellung von Bibliotheksnutzern, bei denen bestimmte Bücher gekauft wurden:

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();

Ansatz auswählen

Der Raum unterstützt beide Ansätze. Sie können also je nachdem, welcher Ansatz für Ihre App am besten geeignet ist. In diesem Abschnitt werden einige der warum Sie vielleicht das eine oder das andere bevorzugen.

Mit dem Ansatz für die Zwischendatenklasse müssen Sie das Schreiben von komplexen SQL-Anweisungen vermeiden. abfragen, kann aber auch die Codekomplexität erhöhen, da die zusätzliche Datenklassen zu erstellen. Kurz gesagt: Der Rückgabetyp „Multimap“ erfordert, dass Ihre SQL-Abfragen mehr Arbeit leisten und die Zwischendaten erfordert Ihren Code mehr Arbeit.

Wenn Sie keinen bestimmten Grund für die Verwendung von Datenklassen der mittleren Stufe haben, empfehlen wir den Multimap-Rückgabetyp. Weitere Informationen über erhalten Sie unter Eine Conversion zurückgeben Multimap.

Im weiteren Verlauf dieses Leitfadens wird gezeigt, wie Beziehungen mithilfe der mit Datenklassen für Fortgeschrittene.

Eingebettete Objekte erstellen

Manchmal möchten Sie eine Entität oder ein Datenobjekt als in Ihrer Datenbanklogik ein zusammenhängendes Ganzes, auch wenn das Objekt mehrere . In diesen Fällen können Sie das @Embedded -Annotation zur Darstellung eines Objekts, das Sie in seine untergeordneten Feldern einer Tabelle. Sie können dann die eingebetteten Felder genau wie gewohnt abfragen. für andere einzelne Spalten.

Die Klasse User kann beispielsweise ein Feld vom Typ Address enthalten, das stellt eine Zusammensetzung von Feldern mit den Namen street, city, state und postCode Um die zusammengesetzten Spalten separat in der Tabelle zu speichern, fügen Sie ein Feld Address in der Klasse User, das mit annotiert ist @Embedded, als wie im folgenden Code-Snippet dargestellt:

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 Folgendem: Namen: id, firstName, street, state, city und post_code.

Wenn eine Entität über mehrere eingebettete Felder desselben Typs verfügt, können Sie jedes Feld beibehalten eindeutig sein, indem Sie den Parameter prefix Property. Der angegebene Wert wird dann am Anfang jeder Spalte hinzugefügt Namen im eingebetteten Objekt ein.

1:1-Beziehungen definieren

Eine 1:1-Beziehung zwischen zwei Entitäten ist eine Beziehung, bei der jede Instanz der übergeordneten Entität entspricht genau einer Instanz der untergeordneten Entität und umgekehrt.

Stellen Sie sich beispielsweise eine Musik-Streaming-App vor, bei der der Nutzer über eine Bibliothek mit eigenen Songs. Jeder Nutzer hat nur eine Bibliothek und jede Bibliothek genau einem Nutzer entspricht. Daher gibt es ein Einzelgespräch Beziehung zwischen der User- und der Library-Entität.

Um eine 1:1-Beziehung zu definieren, erstellen Sie zunächst eine Klasse für jede Ihrer beiden Entitäten. Eine der Entitäten muss eine Variable enthalten, die auf den Primärschlüssel der anderen Entität verweist.

Kotlin

@Entity
data class User(
    @PrimaryKey val userId: Long,
    val name: String,
    val age: Int
)

@Entity
data class Library(
    @PrimaryKey val libraryId: Long,
    val userOwnerId: Long
)

Java

@Entity
public class User {
    @PrimaryKey public long userId;
    public String name;
    public int age;
}

@Entity
public class Library {
    @PrimaryKey public long libraryId;
    public long userOwnerId;
}

Um die Liste der Nutzer und entsprechenden Bibliotheken abzufragen, müssen Sie zuerst um die 1:1-Beziehung zwischen den beiden Entitäten zu modellieren. Erstellen Sie dazu Neue Datenklasse, bei der jede Instanz eine Instanz der übergeordneten Entität und die entsprechende Instanz der untergeordneten Entität. Fügen Sie den @Relation hinzu. an die Instanz der untergeordneten Entität übergeben, wobei parentColumn auf den Namen der Primärschlüsselspalte der übergeordneten Entität und entityColumn ist auf den Namen der Spalte der untergeordneten Entität festgelegt, die auf die übergeordnete Entität verweist. Primärschlüssel der Entität.

Kotlin

data class UserAndLibrary(
    @Embedded val user: User,
    @Relation(
         parentColumn = "userId",
         entityColumn = "userOwnerId"
    )
    val library: Library
)

Java

public class UserAndLibrary {
    @Embedded public User user;
    @Relation(
         parentColumn = "userId",
         entityColumn = "userOwnerId"
    )
    public Library library;
}

Fügen Sie abschließend der DAO-Klasse eine Methode hinzu, die alle Instanzen der Daten zurückgibt Klasse, die die übergeordnete und die untergeordnete Entität koppelt. Diese Methode erfordert Raum zum Ausführen von zwei Abfragen, also fügen Sie hier die Annotation @Transaction hinzu damit der gesamte Vorgang atomar ausgeführt wird.

Kotlin

@Transaction
@Query("SELECT * FROM User")
fun getUsersAndLibraries(): List<UserAndLibrary>

Java

@Transaction
@Query("SELECT * FROM User")
public List<UserAndLibrary> getUsersAndLibraries();

1:n-Beziehungen definieren

Bei einer 1:n-Beziehung zwischen zwei Entitäten Instanz der übergeordneten Entität entspricht null oder mehr Instanzen der untergeordneten Entität Entität. Jede Instanz der untergeordneten Entität kann jedoch nur genau einer Entität entsprechen. Instanz der übergeordneten Entität.

Nehmen wir im Beispiel der Musik-Streaming-App an, dass die Nutzenden die Möglichkeit haben, ihre Songs in Playlists zusammenfassen. Jeder Nutzer kann beliebig viele Playlists erstellen. aber jede Playlist wird von genau einem Nutzer erstellt. Daher gibt es eine 1:n-Beziehung zwischen der User-Entität und der Playlist-Entität.

Um eine 1:n-Beziehung zu definieren, erstellen Sie zunächst eine Klasse für die beiden Entitäten. Wie bei einer 1:1-Beziehung muss die untergeordnete Entität eine Variable enthalten, die ist ein Verweis auf den Primärschlüssel der übergeordneten Entität.

Kotlin

@Entity
data class User(
    @PrimaryKey val userId: Long,
    val name: String,
    val age: Int
)

@Entity
data class Playlist(
    @PrimaryKey val playlistId: Long,
    val userCreatorId: Long,
    val playlistName: String
)

Java

@Entity
public class User {
    @PrimaryKey public long userId;
    public String name;
    public int age;
}

@Entity
public class Playlist {
    @PrimaryKey public long playlistId;
    public long userCreatorId;
    public String playlistName;
}

Um die Liste der Nutzer und der entsprechenden Playlists abzufragen, musst du zuerst um die 1:n-Beziehung zwischen den beiden Entitäten zu modellieren. Erstellen Sie dazu eine neue Datenklasse, bei der jede Instanz eine Instanz der übergeordneten Entität und Liste aller entsprechenden Instanzen der untergeordneten Entität. Fügen Sie den @Relation hinzu. an die Instanz der untergeordneten Entität übergeben, wobei parentColumn auf den Namen der Primärschlüsselspalte der übergeordneten Entität und entityColumn ist auf den Namen der Spalte der untergeordneten Entität festgelegt, die auf die übergeordnete Entität verweist. Primärschlüssel der Entität.

Kotlin

data class UserWithPlaylists(
    @Embedded val user: User,
    @Relation(
          parentColumn = "userId",
          entityColumn = "userCreatorId"
    )
    val playlists: List<Playlist>
)

Java

public class UserWithPlaylists {
    @Embedded public User user;
    @Relation(
         parentColumn = "userId",
         entityColumn = "userCreatorId"
    )
    public List<Playlist> playlists;
}

Fügen Sie abschließend der DAO-Klasse eine Methode hinzu, die alle Instanzen der Daten zurückgibt Klasse, die die übergeordnete und die untergeordnete Entität koppelt. Diese Methode erfordert Raum zum Ausführen von zwei Abfragen, also fügen Sie hier die Annotation @Transaction hinzu damit der gesamte Vorgang atomar ausgeführt wird.

Kotlin

@Transaction
@Query("SELECT * FROM User")
fun getUsersWithPlaylists(): List<UserWithPlaylists>

Java

@Transaction
@Query("SELECT * FROM User")
public List<UserWithPlaylists> getUsersWithPlaylists();

m:n-Beziehungen definieren

Bei einer n:n-Beziehung zwischen zwei Entitäten Instanz der übergeordneten Entität entspricht null oder mehr Instanzen der untergeordneten Entität und umgekehrt.

Betrachten Sie im Beispiel der Musikstreaming-App die Titel in den benutzerdefinierten Playlists. Jede Playlist kann viele Songs enthalten und jeder Song kann Teil von vielen verschiedene Playlists erstellen. Daher besteht eine n:n-Beziehung zwischen der Playlist- und der Song-Entität.

Um eine n:n-Beziehung zu definieren, erstellen Sie zunächst eine Klasse für jede Ihrer beiden Entitäten. N-zu-N-Beziehungen unterscheiden sich von anderen Beziehungstypen, da in der Regel Verweis auf die übergeordnete Entität in der untergeordneten Entität Erstellen Sie stattdessen eine dritte Klasse zur Darstellung einer assoziativen Entität oder eines Querverweises zwischen den beiden Entitäten einfügen. Die Querverweistabelle muss Spalten für Primärschlüssel jeder Entität in der m:n-Beziehung, die in in der Tabelle. In diesem Beispiel entspricht jede Zeile in der Vergleichstabelle ein Paar aus einer Playlist-Instanz und einer Song-Instanz, wobei die referenzierte Instanz Song ist in der entsprechenden Playlist enthalten.

Kotlin

@Entity
data class Playlist(
    @PrimaryKey val playlistId: Long,
    val playlistName: String
)

@Entity
data class Song(
    @PrimaryKey val songId: Long,
    val songName: String,
    val artist: String
)

@Entity(primaryKeys = ["playlistId", "songId"])
data class PlaylistSongCrossRef(
    val playlistId: Long,
    val songId: Long
)

Java

@Entity
public class Playlist {
    @PrimaryKey public long playlistId;
    public String playlistName;
}

@Entity
public class Song {
    @PrimaryKey public long songId;
    public String songName;
    public String artist;
}

@Entity(primaryKeys = {"playlistId", "songId"})
public class PlaylistSongCrossRef {
    public long playlistId;
    public long songId;
}

Der nächste Schritt hängt davon ab, wie Sie diese verwandten Entitäten abfragen möchten.

  • Wenn du Playlists und eine Liste der entsprechenden Songs für eine Playlist abfragen möchtest, Erstellen Sie für jede Playlist eine neue Datenklasse, die ein einzelnes Playlist-Objekt enthält. und eine Liste aller Song-Objekte, die die Playlist enthält.
  • Wenn du nur Songs und eine Liste der entsprechenden Playlists abfragen möchtest, Erstellen Sie jeweils eine neue Datenklasse, die ein einzelnes Song-Objekt und eine Liste Playlist-Objekten enthält, in dem der Song vorkommt.

In beiden Fällen modellieren Sie die Beziehung zwischen den Entitäten mithilfe der Funktion associateBy-Eigenschaft in der Annotation @Relation in den folgenden diese Klassen, um die Querverweisentität zu identifizieren, die die Beziehung bereitstellt. zwischen der Playlist- und der Song-Entität.

Kotlin

data class PlaylistWithSongs(
    @Embedded val playlist: Playlist,
    @Relation(
         parentColumn = "playlistId",
         entityColumn = "songId",
         associateBy = Junction(PlaylistSongCrossRef::class)
    )
    val songs: List<Song>
)

data class SongWithPlaylists(
    @Embedded val song: Song,
    @Relation(
         parentColumn = "songId",
         entityColumn = "playlistId",
         associateBy = Junction(PlaylistSongCrossRef::class)
    )
    val playlists: List<Playlist>
)

Java

public class PlaylistWithSongs {
    @Embedded public Playlist playlist;
    @Relation(
         parentColumn = "playlistId",
         entityColumn = "songId",
         associateBy = @Junction(PlaylistSongCrossref.class)
    )
    public List<Song> songs;
}

public class SongWithPlaylists {
    @Embedded public Song song;
    @Relation(
         parentColumn = "songId",
         entityColumn = "playlistId",
         associateBy = @Junction(PlaylistSongCrossref.class)
    )
    public List<Playlist> playlists;
}

Fügen Sie schließlich der DAO-Klasse eine Methode hinzu, um die Abfragefunktionalität App-Anforderungen.

  • getPlaylistsWithSongs: Diese Methode fragt die Datenbank ab und gibt alle Die resultierenden PlaylistWithSongs-Objekte.
  • getSongsWithPlaylists: Diese Methode fragt die Datenbank ab und gibt alle Die resultierenden SongWithPlaylists-Objekte.

Für diese Methoden muss Room jeweils zwei Abfragen ausführen. Fügen Sie also den Parameter @Transaction-Annotation zu beiden Methoden hinzufügen, sodass die gesamte wird in kleinstmöglichen Schritten ausgeführt.

Kotlin

@Transaction
@Query("SELECT * FROM Playlist")
fun getPlaylistsWithSongs(): List<PlaylistWithSongs>

@Transaction
@Query("SELECT * FROM Song")
fun getSongsWithPlaylists(): List<SongWithPlaylists>

Java

@Transaction
@Query("SELECT * FROM Playlist")
public List<PlaylistWithSongs> getPlaylistsWithSongs();

@Transaction
@Query("SELECT * FROM Song")
public List<SongWithPlaylists> getSongsWithPlaylists();

Verschachtelte Beziehungen definieren

Manchmal müssen Sie eine Reihe von drei oder mehr Tabellen abfragen, die zusammengehören. In diesem Fall definierst du verschachtelte Beziehungen. zwischen den Tabellen.

Angenommen, Sie möchten in der Musik-Streaming-App alle Nutzer, alle Playlists für jeden Nutzer und alle Songs in jeder Playlist für jeden Nutzer. Nutzer haben eine 1:n-Beziehung mit Playlists und Playlists eine m:n-Beziehung mit Songs. Im folgenden Codebeispiel sehen Sie die Klassen, die diese Entitäten sowie in der Vergleichstabelle für die m:n-Beziehung zwischen Playlists und Songs:

Kotlin

@Entity
data class User(
    @PrimaryKey val userId: Long,
    val name: String,
    val age: Int
)

@Entity
data class Playlist(
    @PrimaryKey val playlistId: Long,
    val userCreatorId: Long,
    val playlistName: String
)

@Entity
data class Song(
    @PrimaryKey val songId: Long,
    val songName: String,
    val artist: String
)

@Entity(primaryKeys = ["playlistId", "songId"])
data class PlaylistSongCrossRef(
    val playlistId: Long,
    val songId: Long
)

Java

@Entity
public class User {
    @PrimaryKey public long userId;
    public String name;
    public int age;
}

@Entity
public class Playlist {
    @PrimaryKey public long playlistId;
    public long userCreatorId;
    public String playlistName;
}
@Entity
public class Song {
    @PrimaryKey public long songId;
    public String songName;
    public String artist;
}

@Entity(primaryKeys = {"playlistId", "songId"})
public class PlaylistSongCrossRef {
    public long playlistId;
    public long songId;
}

Modellieren Sie zunächst die Beziehung zwischen zwei Tabellen in Ihrem Dataset, mit einer Datenklasse und dem @Relation-Anmerkung. Die Das folgende Beispiel zeigt eine PlaylistWithSongs-Klasse, die eine m:n-Beziehung modelliert Beziehung zwischen der Playlist-Entitätsklasse und der Song-Entitätsklasse:

Kotlin

data class PlaylistWithSongs(
    @Embedded val playlist: Playlist,
    @Relation(
         parentColumn = "playlistId",
         entityColumn = "songId",
         associateBy = Junction(PlaylistSongCrossRef::class)
    )
    val songs: List<Song>
)

Java

public class PlaylistWithSongs {
    @Embedded public Playlist playlist;
    @Relation(
         parentColumn = "playlistId",
         entityColumn = "songId",
         associateBy = Junction(PlaylistSongCrossRef.class)
    )
    public List<Song> songs;
}

Nachdem Sie eine Datenklasse definiert haben, die diese Beziehung darstellt, erstellen Sie eine weitere Datenklasse, die die Beziehung zwischen einer anderen Tabelle aus Ihrem Dataset und die erste Beziehungsklasse, „Verschachtelung“, Beziehung innerhalb des neuen eins. Das folgende Beispiel zeigt eine UserWithPlaylistsAndSongs-Klasse, die ein Modell eine 1:n-Beziehung zwischen der User-Entitätsklasse und dem PlaylistWithSongs-Beziehungsklasse:

Kotlin

data class UserWithPlaylistsAndSongs(
    @Embedded val user: User
    @Relation(
        entity = Playlist::class,
        parentColumn = "userId",
        entityColumn = "userCreatorId"
    )
    val playlists: List<PlaylistWithSongs>
)

Java

public class UserWithPlaylistsAndSongs {
    @Embedded public User user;
    @Relation(
        entity = Playlist.class,
        parentColumn = "userId",
        entityColumn = "userCreatorId"
    )
    public List<PlaylistWithSongs> playlists;
}

Die UserWithPlaylistsAndSongs-Klasse modelliert die Beziehungen indirekt. zwischen den drei Entitätsklassen User, Playlist und Song. Dies ist wie in Abbildung 1 dargestellt.

UserWithPlaylistsAndSongs modelliert die Beziehung zwischen User und
  „PlaylistWithSongs“, das wiederum die Beziehung zwischen „Playlist“
  und Song.

Abbildung 1: Diagramm der Beziehungsklassen in der Beispiel für eine Musikstreaming-App

Wenn Ihr Dataset weitere Tabellen enthält, erstellen Sie eine Klasse zur Modellierung der Beziehung zwischen jeder verbleibenden Tabelle und der Beziehungsklasse, die modelliert, Beziehungen zwischen allen vorherigen Tabellen. Dadurch wird eine Kette verschachtelter Beziehungen zwischen allen Tabellen, die Sie abfragen möchten.

Fügen Sie schließlich der DAO-Klasse eine Methode hinzu, um die Abfragefunktionalität anzuzeigen, die die Ihre App braucht. Bei dieser Methode muss Room mehrere Abfragen ausführen. Fügen Sie also den Parameter @Transaction-Anmerkung damit der gesamte Vorgang atomar ausgeführt wird:

Kotlin

@Transaction
@Query("SELECT * FROM User")
fun getUsersWithPlaylistsAndSongs(): List<UserWithPlaylistsAndSongs>

Java

@Transaction
@Query("SELECT * FROM User")
public List<UserWithPlaylistsAndSongs> getUsersWithPlaylistsAndSongs();

Weitere Informationen

Weitere Informationen zum Definieren von Beziehungen zwischen Entitäten in Room finden Sie in der wenn Sie zusätzliche Ressourcen nutzen.

Produktproben

Videos

Blogs