تحديد العلاقات بين العناصر

نظرًا لأن SQLite عبارة عن قاعدة بيانات علائقية، يمكنك تعريف العلاقات بين الكيانات. ولكن في حين تسمح معظم مكتبات التعيينات العلائقية للكائنات بالرجوع إلى بعضها البعض، تحظر الغرفة ذلك صراحةً. لمعرفة السبب الفني وراء هذا القرار، يُرجى الاطّلاع على القسم فهم سبب عدم السماح في Room بالإحالة إلى العناصر.

نهجان محتملان

في الغرفة، هناك طريقتان لتحديد العلاقة بين الكيانات والاستعلام عنها: إما باستخدام فئة بيانات متوسطة مع كائنات مضمنة أو طريقة طلب بحث علائقي مع نوع عرض خرائط متعددة.

فئة البيانات المتوسطة

في نهج فئة البيانات المتوسطة، يمكنك تحديد فئة بيانات تشكِّل العلاقة بين كيانات الغرفة. تحتوي فئة البيانات هذه على الأزواج بين مثيلات كيان ومثيلات كيان آخر ككائنات مضمّنة. يمكن لطرق الاستعلام بعد ذلك عرض مثيلات لفئة البيانات هذه للاستخدام في تطبيقك.

على سبيل المثال، يمكنك تحديد فئة بيانات UserBook لتمثيل مستخدمي المكتبات الذين لديهم كتب معيّنة تم سحبها، وتحديد طريقة طلب بحث لاسترداد قائمة UserBook مثيل من قاعدة البيانات:

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

أنواع الإرجاع إلى الخرائط المتعددة

في نهج نوع إرجاع الخرائط المتعددة، لا تحتاج إلى تحديد أي فئات بيانات إضافية. بدلاً من ذلك، يمكنك تحديد نوع عرض multimap لطريقتك استنادًا إلى بنية الخريطة التي تريدها وتحديد العلاقة بين الكيانات مباشرةً في طلب بحث SQL.

على سبيل المثال، تعرض طريقة طلب البحث التالية تعيين User وBook لتمثيل مستخدمي المكتبة من خلال كتب محددة تم دفع رسومها:

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

اختيار طريقة

يدعم Room هذين الأسلوبين، بحيث يمكنك استخدام الطريقة الأنسب لتطبيقك. يناقش هذا القسم بعض الأسباب التي قد تجعلك تفضل أحدهما أو الآخر.

يتيح لك نهج فئة البيانات المتوسطة تجنب كتابة استعلامات SQL معقدة، ولكن يمكن أن يؤدي أيضًا إلى زيادة تعقيد التعليمة البرمجية بسبب فئات البيانات الإضافية التي يتطلبها. باختصار، يتطلب نهج نوع الإرجاع المتعدد الخرائط أن تقوم استعلامات SQL بإجراء المزيد من العمل، ونهج فئة البيانات الوسيطة يتطلب التعليمات البرمجية الخاصة بك للقيام بمزيد من العمل.

إذا لم يكن لديك سبب محدّد لاستخدام فئات البيانات الوسيطة، ننصحك باستخدام أسلوب نوع الإرجاع المتعدد الخرائط. للمزيد من المعلومات حول هذا النهج، يمكنك مراجعة عرض خريطة متعددة.

يوضح بقية هذا الدليل كيفية تعريف العلاقات باستخدام نهج فئة البيانات المتوسط.

إنشاء كائنات مضمّنة

قد ترغب أحيانًا في التعبير عن كيان أو كائن بيانات ككل متماسك في منطق قاعدة البيانات، حتى لو كان الكائن يحتوي على عدة حقول. في هذه الحالات، يمكنك استخدام التعليق التوضيحي @Embedded لتمثيل كائن تريد تحليله إلى حقوله الفرعية داخل الجدول. يمكنك بعد ذلك الاستعلام عن الحقول المضمنة تمامًا كما تفعل مع الأعمدة الفردية الأخرى.

على سبيل المثال، يمكن أن تتضمّن الفئة User حقلاً من النوع Address يمثّل تركيبة حقول باسم street وcity وstate وpostCode. لتخزين الأعمدة التي تم إنشاؤها بشكلٍ منفصل في الجدول، ضمِّن الحقل Address في الفئة User التي تمت إضافة تعليق توضيحي لها باستخدام @Embedded، كما هو موضّح في مقتطف الرمز التالي:

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

ويحتوي الجدول الذي يمثل كائن User بعد ذلك على أعمدة بالأسماء التالية: id وfirstName وstreet وstate وcity وpost_code.

إذا كان أحد الكيانات يحتوي على عدة حقول مضمّنة من النوع نفسه، يمكنك إبقاء كل عمود فريدًا من خلال ضبط السمة prefix. تضيف Room بعد ذلك القيمة المقدَّمة إلى بداية كل اسم عمود في العنصر المضمّن.

تعريف العلاقات الفردية

العلاقة من واحد إلى واحد بين كيانين هي علاقة يتجاوب فيها كل مثيل للكيان الأصلي مع مثيل واحد من الكيان الفرعي تمامًا، والعكس صحيح أيضًا.

على سبيل المثال، فكّر في تطبيق لبث الموسيقى حيث تتوفر للمستخدم مكتبة من الأغاني التي يملكها. لكل مستخدم مكتبة واحدة فقط، وكل مكتبة تتوافق مع مستخدم واحد بالضبط. لذلك، توجد علاقة واحد لواحد بين الكيان User والكيان Library.

لتعريف علاقة واحد لواحد، قم أولاً بإنشاء فئة لكل من الكيانين. يجب أن يتضمن أحد الكيانات متغيرًا يشير إلى المفتاح الأساسي للكيان الآخر.

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

للاستعلام عن قائمة المستخدمين والمكتبات المقابلة، يجب عليك أولاً إنشاء نموذج للعلاقة الفردية بين الكيانين. للقيام بذلك، قم بإنشاء فئة بيانات جديدة حيث يكون لكل مثيل مثيل للكيان الأصلي والمثيل المقابل للكيان الفرعي. أضِف التعليق التوضيحي @Relation إلى مثيل الكيان الفرعي، مع ضبط parentColumn على اسم عمود المفتاح الأساسي للكيان الرئيسي وentityColumn مع ضبط اسم العمود الخاص بالكيان الفرعي الذي يشير إلى المفتاح الأساسي للكيان الأصلي.

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

أخيرًا، أضف طريقة إلى فئة DAO تعرض جميع مثيلات فئة البيانات التي تربط الكيان الأصلي بالكيان الفرعي. تتطلب هذه الطريقة غرفة لتنفيذ طلبَي بحث، لذا أضِف التعليق التوضيحي @Transaction إلى هذه الطريقة حتى يتم تنفيذ العملية برمتها بشكل كامل.

Kotlin

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

Java

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

تعريف العلاقات أحادية ومتعددة

إنّ العلاقة من واحد إلى متعدد بين كيانين هي علاقة يتجاوب فيها كل مثيل للكيان الرئيسي مع صفر أو أكثر من مثيلات الكيان الفرعي، ولكن يمكن لكل مثيل من الكيان الفرعي أن يتوافق مع مثيل واحد فقط للكيان الرئيسي.

في مثال تطبيق بث الموسيقى، افترض أن المستخدم لديه القدرة على تنظيم أغانيه في قوائم تشغيل. يمكن لكل مستخدم إنشاء العدد الذي يريده من قوائم التشغيل، ولكن يتم إنشاء كل قائمة تشغيل بواسطة مستخدم واحد فقط. لذلك، توجد علاقة واحد إلى أطراف بين الكيان User والكيان Playlist.

لتعريف علاقة واحد لمتعدد، أنشئ أولاً فئة للكيانين. كما هو الحال في علاقة واحد لواحد، يجب أن يتضمن الكيان الفرعي متغيرًا يعد مرجعًا إلى المفتاح الأساسي للكيان الأصلي.

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

للاستعلام عن قائمة المستخدمين وقوائم التشغيل المقابلة، يجب أولاً إنشاء نموذج للعلاقة من واحد إلى متعدد بين الكيانين. للقيام بذلك، أنشئ فئة بيانات جديدة يكون فيها كل مثيل مثيل للكيان الأصلي وقائمة بجميع مثيلات الكيان الفرعي المقابلة. أضِف التعليق التوضيحي @Relation إلى مثيل الكيان الفرعي، مع ضبط parentColumn على اسم عمود المفتاح الأساسي للكيان الرئيسي وentityColumn مع ضبط اسم العمود الخاص بالكيان الفرعي الذي يشير إلى المفتاح الأساسي للكيان الأصلي.

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

أخيرًا، أضف طريقة إلى فئة DAO تعرض جميع مثيلات فئة البيانات التي تربط الكيان الأصلي بالكيان الفرعي. تتطلب هذه الطريقة غرفة لتنفيذ طلبَي بحث، لذا أضِف التعليق التوضيحي @Transaction إلى هذه الطريقة حتى يتم تنفيذ العملية برمتها بشكل كامل.

Kotlin

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

Java

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

تعريف علاقات العديد إلى الأطراف

العلاقة من أطراف متعددة بين كيانين هي علاقة يتجاوب فيها كل مثيل للكيان الرئيسي مع صفر أو أكثر من مثيلات الكيان الفرعي، والعكس صحيح أيضًا.

في مثال تطبيق بث الموسيقى، ضع في اعتبارك الأغاني في قوائم التشغيل التي حددها المستخدم. يمكن أن تتضمن كل قائمة تشغيل أغانٍ متعددة، ويمكن أن تكون كل أغنية جزءًا من قوائم تشغيل مختلفة. لذلك، هناك علاقة "أطراف لمت" بين الكيان Playlist والكيان Song.

لتعريف علاقة متعدد لمتعدد، أنشئ أولاً فئة لكل كيان من الكيانين. تختلف العلاقات بين العديد من الأطراف عن أنواع العلاقات الأخرى نظرًا لعدم وجود إشارة بشكل عام إلى الكيان الرئيسي في الكيان الفرعي. بدلاً من ذلك، يمكنك إنشاء فئة ثالثة لتمثيل كيان مرتبط أو جدول المراجع التبادلي بين الكيانَين. يجب أن يحتوي جدول الإسناد الترافقي على أعمدة للمفتاح الأساسي من كل كيان في العلاقة بين أطراف متعددة ممثلة في الجدول. في هذا المثال، يقابل كل صف في جدول الإشارات المرجعية إقران بين مثيل Playlist ومثيل Song حيث يتم تضمين الأغنية المشار إليها في قائمة التشغيل المُشار إليها.

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

تعتمد الخطوة التالية على الطريقة التي تريد بها الاستعلام عن هذه الكيانات ذات الصلة.

  • إذا أردت البحث عن قوائم التشغيل وقائمة الأغاني المقابلة لكل قائمة تشغيل، عليك إنشاء فئة بيانات جديدة تتضمّن عنصر Playlist واحدًا وقائمة بجميع عناصر Song التي تتضمّنها قائمة التشغيل.
  • إذا أردت طلب البحث عن الأغاني وقائمة قوائم التشغيل المناسبة لكل منها، أنشئ فئة بيانات جديدة تتضمّن عنصر Song واحدًا وقائمة بجميع عناصر Playlist التي تتضمّن الأغنية.

في كلتا الحالتين، أنشِئ نموذج للعلاقة بين الكيانات باستخدام السمة associateBy في التعليق التوضيحي @Relation في كل فئة من هذه الفئات لتحديد كيان ذي مرجع تبادلي يوفّر العلاقة بين الكيان Playlist والكيان Song.

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

أخيرًا، أضف طريقة إلى فئة DAO لكشف وظيفة الاستعلام التي يحتاجها تطبيقك.

  • getPlaylistsWithSongs: ترسل هذه الطريقة طلب بحث إلى قاعدة البيانات وتعرض جميع كائنات PlaylistWithSongs الناتجة.
  • getSongsWithPlaylists: ترسل هذه الطريقة طلب بحث إلى قاعدة البيانات وتعرض جميع كائنات SongWithPlaylists الناتجة.

تتطلّب كل طريقة من الطريقتين تطبيق Room لتنفيذ طلبَي بحث، لذا أضِف التعليق التوضيحي @Transaction إلى كلتا الطريقتين حتى يتم تنفيذ العملية بأكملها بشكل كامل.

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

تعريف العلاقات المتداخلة

في بعض الأحيان، قد تحتاج إلى الاستعلام عن مجموعة من ثلاثة جداول أو أكثر ترتبط جميعها ببعضها البعض. في هذه الحالة، يمكنك تحديد العلاقات المتداخلة بين الجداول.

لنفترض أنّه في مثال تطبيق بث الموسيقى، تريد الاستعلام عن جميع المستخدمين، وعن جميع قوائم التشغيل لكل مستخدم، وعن كل الأغاني في كل قائمة تشغيل لكل مستخدم. بين المستخدمين علاقة مترابطة ومتعددة مع قوائم التشغيل، بينما تربط بين قوائم التشغيل علاقة مترابطة ومتعددة مع الأغاني. يوضح مثال الرمز البرمجي التالي الفئات التي تمثل هذه العناصر بالإضافة إلى جدول المقارنة بين قوائم تشغيل متعددة للعلاقة بين قوائم تشغيل وأغانٍ:

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

تشكِّل الفئة UserWithPlaylistsAndSongs بشكل غير مباشر العلاقات بين فئات الكيانات الثلاث: User وPlaylist وSong. تم توضيح ذلك في الشكل 1.

تمثّل ميزة UserWithAccountsAndSongs نموذجًا للعلاقة بين المستخدم
  و&quot;قائمة التشغيل مع الأغاني&quot;، ما يؤدي بدوره إلى إنشاء نموذج للعلاقة بين قائمة التشغيل
  والأغنية.

الشكل 1. رسم بياني لفئات العلاقات في مثال تطبيق بث الموسيقى.

إذا كانت هناك أي جداول أخرى في مجموعتك، فأنشئ فئة لنمذجة العلاقة بين كل جدول متبقٍ وفئة العلاقة التي تصمم العلاقات بين جميع الجداول السابقة. يؤدي هذا إلى إنشاء سلسلة من العلاقات المتداخلة بين جميع الجداول التي تريد الاستعلام عنها.

وأخيرًا، أضف طريقة إلى فئة DAO لعرض وظيفة الاستعلام التي يحتاجها تطبيقك. تتطلّب هذه الطريقة من الغرفة تنفيذ طلبات بحث متعددة، لذا أضِف التعليق التوضيحي @Transaction حتى يتم تنفيذ العملية بأكملها بشكل تفصيلي:

Kotlin

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

Java

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

مراجع إضافية

لمعرفة المزيد حول تعريف العلاقات بين الكيانات في الغرفة، يُرجى الاطّلاع على الموارد الإضافية التالية.

عيّنات

الفيديوهات الطويلة

المدوّنات