מכיוון ש-SQLite הוא מסד נתונים רלציוני, אפשר להגדיר קשרים בין ישויות. אבל בעוד שרוב ספריות המיפוי של אובייקטים רלציוניים מאפשרות לאובייקטים של ישויות להפנות אחד לשני, Room אוסרת זאת באופן מפורש. כדי להבין את ההיגיון הטכני מאחורי ההחלטה הזו, אפשר לעיין במאמר למה אי אפשר להשתמש בהפניות לאובייקטים ב-Room.
סוגי הקשרים
החדר תומך בסוגי הקשר הבאים:
- אחד לאחד: מייצג קשר שבו ישות אחת קשורה לישות אחת אחרת.
- אחד לרבים: מייצג קשר שבו ישות אחת יכולה להיות קשורה לכמה ישויות מסוג אחר.
- רבים לרבים: מייצג קשר שבו ישויות רבות מסוג אחד יכולות להיות קשורות לישויות רבות מסוג אחר. בדרך כלל צריך טבלת צומת.
- קשרי גומלין מקוננים באמצעות אובייקטים מוטמעים: מייצג קשר גומלין שבו ישות אחת מכילה ישות אחרת כמאפיין, והישות המקוננת הזו יכולה להכיל ישויות נוספות. בפעולה הזו נעשה שימוש בהערה
@Embedded.
בחירה בין שתי גישות
ב-Room, יש שתי דרכים להגדיר ולשאול על קשר בין ישויות. אפשר להשתמש באחת מהאפשרויות הבאות:
- מחלקת נתונים ביניים עם אובייקטים מוטמעים, או
- פונקציית שאילתה רלציונית עם סוג החזרה של multimap.
אם אין לכם סיבה ספציפית להשתמש במחלקות נתונים ביניים, מומלץ להשתמש בגישה של סוג החזרה multimap. מידע נוסף על הגישה הזו זמין במאמר החזרת מפה מרובת רמות.
הגישה של מחלקת נתונים ביניים מאפשרת להימנע מכתיבת שאילתות SQL מורכבות, אבל היא גם עלולה להוביל למורכבות מוגברת של הקוד כי היא דורשת מחלקות נתונים נוספות. בקיצור, הגישה של סוג ההחזרה multimap מחייבת את שאילתות ה-SQL לבצע יותר עבודה, והגישה של מחלקת הנתונים הביניים מחייבת את הקוד לבצע יותר עבודה.
שימוש בגישה של מחלקת נתונים ביניים
בגישה של מחלקת נתונים ביניים, מגדירים מחלקת נתונים שממחישה את הקשר בין ישויות Room. סיווג הנתונים הזה מכיל את ההתאמות בין מופעים של ישות אחת לבין מופעים של ישות אחרת כאובייקטים מוטמעים. אחר כך, פונקציות השאילתה יכולות להחזיר מופעים של מחלקת הנתונים הזו לשימוש באפליקציה.
לדוגמה, אפשר להגדיר UserBook מחלקה של נתונים שמייצגת משתמשים בספרייה עם ספרים ספציפיים שהושאלו, ולהגדיר פונקציית שאילתה כדי לאחזר רשימה של מופעים של UserBook ממסד הנתונים:
@Dao interface UserBookDao { @Query( """ SELECT user.name AS userName, book.name AS bookName FROM user JOIN book ON user.id = book.user_id """ ) fun loadUserAndBookNames(): LiveData<List<UserBook>> } data class UserBook(val userName: String, val bookName: String)
שימוש בגישה של סוג החזרה multimap
בגישה של סוג ההחזרה multimap, לא צריך להגדיר עוד מחלקות נתונים. במקום זאת, מגדירים סוג החזרה של multimap לפונקציה על סמך מבנה המפה הרצוי, ומגדירים את הקשר בין הישויות ישירות בשאילתת ה-SQL.
לדוגמה, פונקציית השאילתה הבאה מחזירה מיפוי של מופעי User ו-Book כדי לייצג משתמשי ספרייה עם ספרים ספציפיים שהושאלו:
@Query( """ SELECT * FROM user JOIN book ON user.id = book.user_id """ ) suspend fun loadUserAndBookNames(): Map<User, List<Book>>
בנוסף, עם סוגי החזרה של מיפויים מרובים, אפשר לשלוח שאילתות לגבי קשרים של אחד לאחד שלא כוללים ישות אחרת. פונקציית השאילתה הבאה מחזירה מיפוי של
User ומספר הספרים שהם השאילו באמצעות ההערה
@MapColumn:
@Query( """ SELECT user.*, COUNT(book.id) AS book_count FROM user LEFT JOIN book ON user.id = book.user_id GROUP BY user.id """ ) suspend fun loadUserAndBookCount(): Map<User, @MapColumn(columnName = "book_count") Int>
יצירת אובייקטים מוטמעים
לפעמים רוצים להציג ישות או אובייקט נתונים כיחידה מגובשת בלוגיקה של מסד הנתונים, גם אם האובייקט מכיל כמה מאפיינים. במקרים כאלה, משתמשים בהערת @Embedded כדי לפרק אובייקט למאפייני המשנה שלו בטבלה. אחרי זה תוכלו לשלוח שאילתות לגבי המאפיינים המוטמעים בדיוק כמו לגבי עמודות אחרות.
לדוגמה, המחלקה User יכולה לכלול את המאפיין Address שמייצג קומפוזיציה של המאפיינים street, city, state ו-postCode. כדי לאחסן את העמודות המורכבות בנפרד בטבלה, מוסיפים את ההערה @Embedded למאפיין Address במחלקה User. בקטע הקוד הבא מוצגת ההגדרה הזו:
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? )
הטבלה שמייצגת אובייקט User מכילה עמודות עם השמות הבאים: id, firstName, street, state, city ו-post_code.
אם לישות יש כמה מאפיינים מוטמעים מאותו סוג, אפשר להגדיר את המאפיין prefix כדי שכל עמודה תהיה ייחודית. לאחר מכן, Room מוסיף את הערך שצוין לתחילת כל שם עמודה באובייקט המוטמע.