הגדרה של קשרי גומלין 'הרבה לרבים' ושליחת שאילתות לגבי קשרים כאלה

קשר גומלין מסוג רבים לרבים בין שתי ישויות הוא קשר שבו כל מופע של ישות אם תואם לאפס או יותר מופעים של ישות הצאצא, ולהיפך.

בדוגמה של אפליקציית סטרימינג של מוזיקה, כדאי להתייחס לשירים בפלייליסטים שהוגדרו על ידי המשתמש. כל פלייליסט יכול לכלול הרבה שירים, וכל שיר יכול להשתייך להרבה פלייליסטים. לכן, יש קשר רבים-לרבים בין הישויות Playlist ו-Song.

כדי להגדיר שאילתות של קשרים רבים-לרבים במסד הנתונים:

  1. הגדרת קשר הגומלין: מגדירים את הישויות ואת ישות השיוך, או את טבלת ההפניות הצולבות, כדי לייצג את קשר הגומלין מסוג רבים לרבים.
  2. שליחת שאילתות לגבי הישויות: קובעים איך רוצים לשלוח שאילתות לגבי הישויות הקשורות, ויוצרים מחלקות נתונים שמייצגות את הפלט הרצוי.

הגדרת הקשר

כדי להגדיר קשר רבים לרבים, קודם יוצרים מחלקה לכל אחת משתי הישויות. קשרי גומלין מסוג רבים לרבים שונים מסוגים אחרים של קשרי גומלין, כי בדרך כלל אין הפניה לישות אם בישות הבן. במקום זאת, יוצרים מחלקה שלישית שמייצגת ישות אסוציאטיבית או טבלת הפניות צולבות בין שתי הישויות. בטבלת ההפניה הצולבת צריכות להיות עמודות למפתח הראשי מכל ישות בקשר רבים-לרבים שמיוצג בטבלה. בדוגמה הזו, כל שורה בטבלת הצלבת הנתונים תואמת לזיווג של מופע Playlist ומופע Song שבהם הפלייליסט שאליו מתבצעת ההפניה כולל את השיר שאליו מתבצעת ההפניה.

@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"], indices = [Index("playlistId", "songId")])
data class PlaylistSongCrossRef(
    val playlistId: Long,
    val songId: Long
)

שאילתת הישויות

השלב הבא תלוי באופן שבו רוצים לשאול שאילתות לגבי הישויות הקשורות האלה.

  • אם רוצים לשלוח שאילתה לגבי פלייליסטים ולקבל רשימה של שירים שמתאימים לכל פלייליסט, צריך ליצור מחלקת נתונים חדשה שמכילה אובייקט Playlist אחד ורשימה של אובייקטים Song שהפלייליסט כולל.
  • אם רוצים לשלוח שאילתה לגבי שירים ולקבל רשימה של פלייליסטים תואמים לכל שיר, צריך ליצור מחלקת נתונים חדשה שמכילה אובייקט Song יחיד ורשימה של אובייקטים Playlist שכוללים את השיר.

בכל מקרה, כדי לתאר את הקשר בין הישויות, צריך להשתמש במאפיין associateBy בהערת @Relation בכל אחת מהמחלקות האלה כדי לזהות את ישות ההפניה הצולבת שמספקת את הקשר בין ישות Playlist לישות Song.

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

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

לבסוף, מוסיפים פונקציה למחלקה של אובייקט הגישה לנתונים (DAO) כדי לחשוף את פונקציית השאילתה שהאפליקציה צריכה.

getPlaylistsWithSongs
מריץ שאילתה במסד הנתונים ומחזיר את כל האובייקטים PlaylistWithSongs שמתקבלים.
getSongsWithPlaylists
מבצע שאילתה במסד הנתונים ומחזיר את כל האובייקטים SongWithPlaylists שמתקבלים.

כדי להריץ כל פונקציה, צריך להריץ שתי שאילתות ב-Room. מוסיפים את ההערה @Transaction לשתי הפונקציות כדי להבטיח שהפעולה תתבצע באופן אטומי.

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

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

מפתחות מורכבים

אם מגדירים את הקשר באמצעות מפתחות מורכבים, צריך לציין כמה עמודות בתגי parentColumns ו-entityColumns של הערת @Relation.

אם צריך לציין עמודות ב-Junction, צריך להשתמש גם ב-parentColumns וב-entityColumns בהערה Junction.

בדוגמה הבאה, ל-Playlist יש מפתח ראשי מורכב שכולל את playlistId ואת creatorId. טבלת ההפניות הצולבות PlaylistSongCrossRef כוללת גם את העמודות האלה כדי להפנות לפלייליסט.

@Entity(primaryKeys = ["playlistId", "creatorId"])
data class Playlist(
    val playlistId: Long,
    val creatorId: Long,
    val playlistName: String
)

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

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

data class PlaylistWithSongs(
    @Embedded val playlist: Playlist,
    @Relation(
        parentColumns = ["playlistId", "creatorId"],
        entityColumns = ["songId"],
        associateBy = Junction(PlaylistSongCrossRef::class)
    )
    val songs: List<Song>
)