از آنجایی که SQLite یک پایگاه داده رابطه ای است، می توانید روابط بین موجودیت ها را تعریف کنید. اما در حالی که اکثر کتابخانههای نگاشت شی-رابطهای به اشیاء موجودیت اجازه میدهند به یکدیگر ارجاع دهند، Room به صراحت این کار را ممنوع میکند. برای آشنایی با استدلال فنی پشت این تصمیم، به درک اینکه چرا اتاق به ارجاعات اشیا اجازه نمی دهد مراجعه کنید.
دو رویکرد ممکن
در Room، دو راه برای تعریف و پرس و جو رابطه بین موجودیت ها وجود دارد: استفاده از یک کلاس داده میانی با اشیاء جاسازی شده یا یک روش پرس و جو رابطه ای با نوع بازگشتی چند نقشه.
کلاس داده متوسط
در رویکرد کلاس داده میانی، شما یک کلاس داده تعریف می کنید که رابطه بین موجودیت های اتاق شما را مدل می کند. این کلاس داده جفتها را بین نمونههای یک موجودیت و نمونههای موجودیت دیگر بهعنوان اشیاء تعبیهشده نگه میدارد. سپس روشهای درخواست شما میتوانند نمونههایی از این کلاس داده را برای استفاده در برنامه شما برگردانند.
به عنوان مثال، می توانید یک کلاس داده UserBook
را برای نمایش کاربران کتابخانه با کتاب های خاص بررسی شده تعریف کنید، و یک روش پرس و جو برای بازیابی لیستی از نمونه های UserBook
از پایگاه داده تعریف کنید:
کاتلین
@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?)
جاوا
@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; }
انواع بازگشت چند نقشه
در رویکرد نوع بازگشت چند نقشه، نیازی به تعریف هیچ کلاس داده اضافی ندارید. در عوض، شما یک نوع بازگشت چند نقشه را برای روش خود بر اساس ساختار نقشه ای که می خواهید تعریف می کنید و رابطه بین موجودیت های خود را مستقیماً در پرس و جوی SQL خود تعریف می کنید.
به عنوان مثال، روش پرس و جو زیر نقشهای از نمونههای User
و Book
را برای نمایش کاربران کتابخانه با کتابهای خاص بررسی شده برمیگرداند:
کاتلین
@Query( "SELECT * FROM user" + "JOIN book ON user.id = book.user_id" ) fun loadUserAndBookNames(): Map<User, List<Book>>
جاوا
@Query( "SELECT * FROM user" + "JOIN book ON user.id = book.user_id" ) public Map<User, List<Book>> loadUserAndBookNames();
یک رویکرد را انتخاب کنید
Room از هر دوی این رویکردها پشتیبانی میکند، بنابراین میتوانید از هر رویکردی که برای برنامهتان بهتر است استفاده کنید. این بخش به برخی از دلایلی که چرا ممکن است یکی یا دیگری را ترجیح دهید، بحث می کند.
رویکرد کلاس داده متوسط به شما امکان می دهد از نوشتن پرس و جوهای پیچیده SQL اجتناب کنید، اما همچنین می تواند به دلیل کلاس های داده اضافی مورد نیاز، باعث افزایش پیچیدگی کد شود. به طور خلاصه، رویکرد نوع بازگشتی چند نقشه ای به درخواست های SQL شما برای انجام کارهای بیشتر نیاز دارد، و رویکرد کلاس داده متوسط به کد شما برای انجام کارهای بیشتر نیاز دارد.
اگر دلیل خاصی برای استفاده از کلاس های داده متوسط ندارید، توصیه می کنیم از رویکرد نوع بازگشت چند نقشه استفاده کنید. برای کسب اطلاعات بیشتر در مورد این رویکرد، به بازگشت یک نقشه چندگانه مراجعه کنید.
بقیه این راهنما نحوه تعریف روابط را با استفاده از رویکرد کلاس داده میانی نشان می دهد.
ایجاد اشیاء تعبیه شده
گاهی اوقات، شما می خواهید یک موجودیت یا شی داده را به عنوان یک کل منسجم در منطق پایگاه داده خود بیان کنید، حتی اگر شی حاوی چندین فیلد باشد. در این مواقع، میتوانید از حاشیهنویسی @Embedded
برای نشان دادن شیای که میخواهید به زیر فیلدهای آن در یک جدول تجزیه کنید، استفاده کنید. سپس میتوانید فیلدهای تعبیهشده را پرس و جو کنید، درست همانطور که برای ستونهای دیگر انجام میدهید.
به عنوان مثال، کلاس User
شما میتواند شامل فیلدی از نوع Address
باشد که ترکیبی از فیلدهایی به نامهای street
، city
، state
و postCode
را نشان میدهد. برای ذخیره ستون های تشکیل شده به طور جداگانه در جدول، یک فیلد Address
در کلاس User
اضافه کنید که با @Embedded
حاشیه نویسی شده است، همانطور که در قطعه کد زیر نشان داده شده است:
کاتلین
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? )
جاوا
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; }
سپس جدولی که یک شی User
را نشان میدهد شامل ستونهایی با نامهای زیر است: id
، firstName
، street
، state
، city
و post_code
.
اگر یک موجودیت دارای چندین فیلد جاسازی شده از یک نوع باشد، می توانید با تنظیم ویژگی prefix
، هر ستون را منحصر به فرد نگه دارید. سپس Room مقدار ارائه شده را به ابتدای نام هر ستون در شی جاسازی شده اضافه می کند.
روابط یک به یک را تعریف کنید
رابطه یک به یک بین دو موجودیت رابطه ای است که در آن هر نمونه از موجودیت اصلی دقیقاً با یک نمونه از موجودیت فرزند مطابقت دارد و عکس آن نیز صادق است.
به عنوان مثال، یک برنامه پخش موسیقی را در نظر بگیرید که در آن کاربر کتابخانه ای از آهنگ های خود را دارد. هر کاربر فقط یک کتابخانه دارد و هر کتابخانه دقیقاً مربوط به یک کاربر است. بنابراین، یک رابطه یک به یک بین موجودیت User
و نهاد Library
وجود دارد.
برای تعریف یک رابطه یک به یک، ابتدا برای هر یک از دو موجودیت خود یک کلاس ایجاد کنید. یکی از موجودیت ها باید دارای متغیری باشد که مرجعی به کلید اصلی موجودیت دیگر باشد.
کاتلین
@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 )
جاوا
@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; }
برای جستجو در لیست کاربران و کتابخانه های مربوطه، ابتدا باید رابطه یک به یک بین دو موجودیت را مدل کنید. برای انجام این کار، یک کلاس داده جدید ایجاد کنید که در آن هر نمونه یک نمونه از موجودیت والد و نمونه متناظر از موجودیت فرزند را در خود جای دهد. حاشیهنویسی @Relation
را به نمونه موجودیت فرزند اضافه کنید، با مجموعه parentColumn
به نام ستون کلید اصلی موجودیت والد و مجموعه entityColumn
به نام ستون موجودیت فرزند که به کلید اصلی موجودیت والد ارجاع میدهد.
کاتلین
data class UserAndLibrary( @Embedded val user: User, @Relation( parentColumn = "userId", entityColumn = "userOwnerId" ) val library: Library )
جاوا
public class UserAndLibrary { @Embedded public User user; @Relation( parentColumn = "userId", entityColumn = "userOwnerId" ) public Library library; }
در نهایت، متدی را به کلاس DAO اضافه کنید که تمام نمونه های کلاس داده را که موجودیت والد و موجودیت فرزند را جفت می کند، برمی گرداند. این روش برای اجرای دو کوئری به Room نیاز دارد، بنابراین حاشیه نویسی @Transaction
را به این روش اضافه کنید تا کل عملیات به صورت اتمی انجام شود.
کاتلین
@Transaction @Query("SELECT * FROM User") fun getUsersAndLibraries(): List<UserAndLibrary>
جاوا
@Transaction @Query("SELECT * FROM User") public List<UserAndLibrary> getUsersAndLibraries();
روابط یک به چند را تعریف کنید
رابطه یک به چند بین دو موجودیت رابطه ای است که در آن هر نمونه از موجودیت اصلی با صفر یا چند نمونه از موجودیت فرزند مطابقت دارد، اما هر نمونه از موجودیت فرزند فقط می تواند دقیقاً با یک نمونه از موجودیت اصلی مطابقت داشته باشد.
در مثال برنامه پخش موسیقی، فرض کنید کاربر توانایی سازماندهی آهنگ های خود را در لیست های پخش دارد. هر کاربر می تواند هر تعداد لیست پخش که می خواهد ایجاد کند، اما هر لیست پخش دقیقا توسط یک کاربر ایجاد می شود. بنابراین، یک رابطه یک به چند بین موجودیت User
و موجودیت Playlist
وجود دارد.
برای تعریف رابطه یک به چند، ابتدا یک کلاس برای دو موجودیت ایجاد کنید. همانطور که در رابطه یک به یک، موجودیت فرزند باید متغیری را شامل شود که ارجاع به کلید اصلی موجودیت والد است.
کاتلین
@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 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; }
برای جستجو در لیست کاربران و لیست های پخش مربوطه، ابتدا باید رابطه یک به چند بین این دو موجودیت را مدل کنید. برای انجام این کار، یک کلاس داده جدید ایجاد کنید که در آن هر نمونه یک نمونه از موجودیت والد و لیستی از تمام نمونه های موجودیت فرزند مربوطه را در خود نگه می دارد. حاشیهنویسی @Relation
را به نمونه موجودیت فرزند اضافه کنید، با مجموعه parentColumn
به نام ستون کلید اصلی موجودیت والد و مجموعه entityColumn
به نام ستون موجودیت فرزند که به کلید اصلی موجودیت والد ارجاع میدهد.
کاتلین
data class UserWithPlaylists( @Embedded val user: User, @Relation( parentColumn = "userId", entityColumn = "userCreatorId" ) val playlists: List<Playlist> )
جاوا
public class UserWithPlaylists { @Embedded public User user; @Relation( parentColumn = "userId", entityColumn = "userCreatorId" ) public List<Playlist> playlists; }
در نهایت، متدی را به کلاس DAO اضافه کنید که تمام نمونه های کلاس داده را که موجودیت والد و موجودیت فرزند را جفت می کند، برمی گرداند. این روش برای اجرای دو کوئری به Room نیاز دارد، بنابراین حاشیه نویسی @Transaction
را به این روش اضافه کنید تا کل عملیات به صورت اتمی انجام شود.
کاتلین
@Transaction @Query("SELECT * FROM User") fun getUsersWithPlaylists(): List<UserWithPlaylists>
جاوا
@Transaction @Query("SELECT * FROM User") public List<UserWithPlaylists> getUsersWithPlaylists();
روابط چند به چند را تعریف کنید
رابطه چند به چند بین دو موجودیت، رابطه ای است که در آن هر نمونه از موجودیت اصلی با صفر یا چند نمونه از موجودیت فرزند مطابقت دارد و عکس آن نیز صادق است.
در مثال برنامه پخش موسیقی، آهنگ های موجود در لیست پخش تعریف شده توسط کاربر را در نظر بگیرید. هر لیست پخش می تواند شامل آهنگ های زیادی باشد و هر آهنگ می تواند بخشی از لیست های پخش مختلف باشد. بنابراین، بین موجودیت Playlist
و موجودیت Song
رابطه بسیار به چند وجود دارد.
برای تعریف رابطه چند به چند، ابتدا برای هر یک از دو موجودیت خود یک کلاس ایجاد کنید. روابط چند به چند از دیگر انواع روابط متمایز هستند زیرا به طور کلی هیچ اشاره ای به موجودیت والد در موجودیت فرزند وجود ندارد. در عوض، یک کلاس سوم برای نشان دادن یک موجودیت انجمنی یا جدول مرجع متقابل بین دو موجود ایجاد کنید. جدول مرجع متقابل باید دارای ستون هایی برای کلید اصلی از هر موجودیت در رابطه چند به چند نشان داده شده در جدول باشد. در این مثال، هر ردیف در جدول ارجاع متقابل مربوط به جفتی از یک 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"]) data class PlaylistSongCrossRef( val playlistId: Long, val songId: Long )
جاوا
@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; }
مرحله بعدی بستگی به این دارد که چگونه می خواهید این موجودیت های مرتبط را پرس و جو کنید.
- اگر میخواهید فهرستهای پخش و فهرستی از آهنگهای مربوطه را برای هر فهرست پخش جستجو کنید، یک کلاس داده جدید ایجاد کنید که شامل یک شی
Playlist
و فهرستی از همه اشیاءSong
است که فهرست پخش شامل میشود. - اگر میخواهید آهنگها و فهرستی از فهرستهای پخش مربوطه را برای هرکدام پرس و جو کنید، یک کلاس داده جدید ایجاد کنید که حاوی یک شی
Song
و فهرستی از همه اشیاءPlaylist
است که آهنگ در آن گنجانده شده است.
در هر صورت، با استفاده از ویژگی associateBy
در حاشیهنویسی @Relation
در هر یک از این کلاسها، رابطه بین موجودیتها را مدلسازی کنید تا موجودیت مرجع متقابل را شناسایی کنید که رابطه بین موجودیت Playlist
و موجودیت Song
را فراهم میکند.
کاتلین
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> )
جاوا
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; }
در نهایت، روشی را به کلاس DAO اضافه کنید تا عملکرد درخواستی مورد نیاز برنامه شما را آشکار کند.
-
getPlaylistsWithSongs
: این روش پایگاه داده را پرس و جو می کند و تمام اشیاءPlaylistWithSongs
حاصل را برمی گرداند. -
getSongsWithPlaylists
: این روش پایگاه داده را پرس و جو می کند و تمام اشیاءSongWithPlaylists
حاصل را برمی گرداند.
هر کدام از این روش ها به Room برای اجرای دو کوئری نیاز دارند، بنابراین حاشیه نویسی @Transaction
را به هر دو روش اضافه کنید تا کل عملیات به صورت اتمی انجام شود.
کاتلین
@Transaction @Query("SELECT * FROM Playlist") fun getPlaylistsWithSongs(): List<PlaylistWithSongs> @Transaction @Query("SELECT * FROM Song") fun getSongsWithPlaylists(): List<SongWithPlaylists>
جاوا
@Transaction @Query("SELECT * FROM Playlist") public List<PlaylistWithSongs> getPlaylistsWithSongs(); @Transaction @Query("SELECT * FROM Song") public List<SongWithPlaylists> getSongsWithPlaylists();
روابط تودرتو را تعریف کنید
گاهی اوقات، ممکن است لازم باشد مجموعه ای از سه یا چند جدول را که همگی به یکدیگر مرتبط هستند پرس و جو کنید. در آن صورت، شما روابط تودرتو بین جداول را تعریف می کنید.
فرض کنید در مثال برنامه پخش موسیقی، میخواهید از همه کاربران، همه لیستهای پخش برای هر کاربر، و همه آهنگهای هر فهرست پخش برای هر کاربر پرس و جو کنید. کاربران با لیست های پخش رابطه یک به چند دارند و لیست های پخش با آهنگ ها رابطه چند به چند دارند. مثال کد زیر کلاسهایی را نشان میدهد که این موجودیتها را نشان میدهند و همچنین جدول مرجع متقابل را برای رابطه چند به چند بین لیستهای پخش و آهنگها نشان میدهد:
کاتلین
@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 )
جاوا
@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
مدل می کند:
کاتلین
data class PlaylistWithSongs( @Embedded val playlist: Playlist, @Relation( parentColumn = "playlistId", entityColumn = "songId", associateBy = Junction(PlaylistSongCrossRef::class) ) val songs: List<Song> )
جاوا
public class PlaylistWithSongs { @Embedded public Playlist playlist; @Relation( parentColumn = "playlistId", entityColumn = "songId", associateBy = Junction(PlaylistSongCrossRef.class) ) public List<Song> songs; }
پس از تعریف یک کلاس داده ای که این رابطه را نشان می دهد، کلاس داده دیگری ایجاد کنید که رابطه بین جدول دیگری را از مجموعه شما و کلاس رابطه اول مدل می کند و رابطه موجود را در جدول جدید "تودرتو" می کند. مثال زیر یک کلاس UserWithPlaylistsAndSongs
را نشان می دهد که یک رابطه یک به چند بین کلاس موجودیت User
و کلاس رابطه PlaylistWithSongs
را مدل می کند:
کاتلین
data class UserWithPlaylistsAndSongs( @Embedded val user: User @Relation( entity = Playlist::class, parentColumn = "userId", entityColumn = "userCreatorId" ) val playlists: List<PlaylistWithSongs> )
جاوا
public class UserWithPlaylistsAndSongs { @Embedded public User user; @Relation( entity = Playlist.class, parentColumn = "userId", entityColumn = "userCreatorId" ) public List<PlaylistWithSongs> playlists; }
کلاس UserWithPlaylistsAndSongs
به طور غیرمستقیم روابط بین هر سه کلاس موجودیت را مدل می کند: User
، Playlist
و Song
. این در شکل 1 نشان داده شده است.
اگر جداول بیشتری در مجموعه شما وجود دارد، یک کلاس برای مدل سازی رابطه بین هر جدول باقیمانده و کلاس رابطه ایجاد کنید که روابط بین تمام جداول قبلی را مدل می کند. این یک زنجیره ای از روابط تودرتو در بین تمام جدول هایی که می خواهید پرس و جو کنید ایجاد می کند.
در نهایت، روشی را به کلاس DAO اضافه کنید تا عملکرد کوئری مورد نیاز برنامه شما را آشکار کند. این روش برای اجرای چندین کوئری به Room نیاز دارد، بنابراین حاشیه نویسی @Transaction
را اضافه کنید تا کل عملیات به صورت اتمی انجام شود:
کاتلین
@Transaction @Query("SELECT * FROM User") fun getUsersWithPlaylistsAndSongs(): List<UserWithPlaylistsAndSongs>
جاوا
@Transaction @Query("SELECT * FROM User") public List<UserWithPlaylistsAndSongs> getUsersWithPlaylistsAndSongs();
منابع اضافی
برای اطلاعات بیشتر در مورد تعریف روابط بین موجودات در اتاق، به منابع اضافی زیر مراجعه کنید.
نمونه ها
ویدیوها
- چیزهای جدید در اتاق (Android Dev Summit '19)