לפעמים, יכול להיות שתצטרכו להריץ שאילתה על קבוצה של שלוש טבלאות או יותר שכולן קשורות זו לזו. במקרה כזה, מגדירים קשרי גומלין מקוננים בין הטבלאות.
נניח שבדוגמה של אפליקציית סטרימינג המוזיקה, אתם רוצים לשלוח שאילתה לגבי כל המשתמשים, כל הפלייליסטים של כל משתמש וכל השירים בכל פלייליסט של כל משתמש. למשתמשים יש קשר של אחד לרבים עם פלייליסטים, ולפלייליסטים יש קשר של רבים לרבים עם שירים. בדוגמה הבאה של קוד אפשר לראות את המחלקות שמייצגות את הישויות האלה, וגם את טבלת ההפניות הצולבות של הקשרים מסוג רבים לרבים בין פלייליסטים לבין שירים:
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;
}
קודם כול, יוצרים מודל של הקשר בין שתיים מהטבלאות בסט, כמו שעושים בדרך כלל, באמצעות מחלקת נתונים וההערה @Relation. בדוגמה הבאה מוצגת מחלקת PlaylistWithSongs שמדמה קשר רבים לרבים בין מחלקת הישות Playlist למחלקת הישות Song:
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;
}
אחרי שמגדירים מחלקה של נתונים שמייצגת את הקשר הזה, יוצרים עוד מחלקה של נתונים שמדמה את הקשר בין טבלה אחרת מהקבוצה לבין מחלקת הקשר הראשונה, כלומר 'מקננים' את הקשר הקיים בתוך הקשר החדש. בדוגמה הבאה מוצגת מחלקה UserWithPlaylistsAndSongs שמדמה קשר של אחד לרבים בין מחלקת הישות User לבין מחלקת הקשר PlaylistWithSongs:
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;
}
הקשרים בין שלושת סוגי הישויות: User, Playlist ו-Song מיוצגים באופן עקיף על ידי המחלקה UserWithPlaylistsAndSongs. האיור 1 ממחיש את זה.
אם יש עוד טבלאות במערך, יוצרים מחלקה כדי למדל את הקשר בין כל טבלה שנותרה לבין מחלקת הקשר שממדלת את הקשרים בין כל הטבלאות הקודמות. הפעולה הזו יוצרת שרשרת של קשרים מוכללים בין כל הטבלאות שרוצים לשלוח להן שאילתה.
לבסוף, מוסיפים שיטה למחלקת ה-DAO כדי לחשוף את פונקציית השאילתה שהאפליקציה צריכה. השיטה הזו דורשת מ-Room להריץ כמה שאילתות, לכן צריך להוסיף את ההערה @Transaction כדי שהפעולה כולה תתבצע באופן אטומי:
Kotlin
@Transaction
@Query("SELECT * FROM User")
fun getUsersWithPlaylistsAndSongs(): List<UserWithPlaylistsAndSongs>
Java
@Transaction
@Query("SELECT * FROM User")
public List<UserWithPlaylistsAndSongs> getUsersWithPlaylistsAndSongs();