الوصول إلى البيانات باستخدام وحدات إدارة البيانات (DAOs) للغرفة

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

باستخدام DAOs للوصول إلى قاعدة بيانات تطبيقك بدلاً من أدوات إنشاء طلبات البحث أو طلبات البحث المباشرة، يمكنك الحفاظ على فصل الاستفسارات، وهو مبدأ معماري مهم. تسهل لك جداول البيانات المنظمة (DAO) أيضًا محاكاة الوصول إلى قاعدة البيانات عند اختبار تطبيقك.

تشريح DAO

يمكنك تعريف كل DAO كواجهة أو فئة مجردة. في حالات الاستخدام الأساسي، عادةً ما تستخدم واجهة. في كلتا الحالتين، يجب دائمًا إضافة التعليقات التوضيحية لقوائم DAO باستخدام @Dao. لا تحتوي أنظمة إدارة البيانات (DAO) على خصائص، لكنها تحدد طريقة واحدة أو أكثر للتفاعل مع البيانات في قاعدة بيانات تطبيقك.

الرمز التالي هو مثال على عنصر DAO بسيط يحدّد طرقًا لإدراج عناصر User وحذفها واختيارها في قاعدة بيانات غرفة:

Kotlin

@Dao
interface UserDao {
    @Insert
    fun insertAll(vararg users: User)

    @Delete
    fun delete(user: User)

    @Query("SELECT * FROM user")
    fun getAll(): List<User>
}

Java

@Dao
public interface UserDao {
    @Insert
    void insertAll(User... users);

    @Delete
    void delete(User user);

    @Query("SELECT * FROM user")
    List<User> getAll();
}

هناك نوعان من طرق DAO التي تحدد تفاعلات قاعدة البيانات:

  • هي طرق مريحة تتيح لك إدراج الصفوف وتحديثها وحذفها في قاعدة البيانات دون كتابة أي تعليمات برمجية لـ SQL.
  • طرق الاستعلام التي تتيح لك كتابة استعلام SQL الخاص بك للتفاعل مع قاعدة البيانات.

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

الطرق المناسبة

يوفر تطبيق Room التعليقات التوضيحية المناسبة لتحديد الطرق التي تُجري عمليات إدراج وتعديلات وعمليات حذف بسيطة بدون الحاجة إلى كتابة عبارة SQL.

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

إدراج

يتيح لك التعليق التوضيحي @Insert تحديد الطرق التي تُدرِج معلَماتها في الجدول المناسب في قاعدة البيانات. يُظهر الرمز التالي أمثلة على طرق @Insert صالحة تُدرج كائن User واحدًا أو أكثر في قاعدة البيانات:

Kotlin

@Dao
interface UserDao {
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun insertUsers(vararg users: User)

    @Insert
    fun insertBothUsers(user1: User, user2: User)

    @Insert
    fun insertUsersAndFriends(user: User, friends: List<User>)
}

Java

@Dao
public interface UserDao {
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    public void insertUsers(User... users);

    @Insert
    public void insertBothUsers(User user1, User user2);

    @Insert
    public void insertUsersAndFriends(User user, List<User> friends);
}

يجب أن تكون كل مَعلمة لطريقة @Insert إما مثيلاً من فئة كيان بيانات الغرفة التي تم التعليق عليها باستخدام @Entity أو مجموعة من مثيلات فئة كيانات البيانات، حيث يشير كل منها إلى قاعدة بيانات. عند استدعاء طريقة @Insert، تدرج Room كل مثيل كيان تم تمريره في جدول قاعدة البيانات المقابل.

إذا تلقت الطريقة @Insert معلَمة واحدة، يمكنها عرض قيمة long، وهي قيمة rowId الجديدة للعنصر المُدرَج. إذا كانت المَعلمة عبارة عن مصفوفة أو مجموعة، يجب عرض صفيف أو مجموعة من قيم long بدلاً من ذلك، مع عرض كل قيمة باعتبارها rowId لأحد العناصر المدرَجة. لمعرفة المزيد حول عرض قيم rowId، راجِع الوثائق المرجعية للتعليق التوضيحي @Insert ووثائق SQLite للجداول الصفية.

تعديل

يتيح لك التعليق التوضيحي @Update تحديد الطرق التي تعدِّل صفوفًا معيَّنة في جدول قاعدة بيانات. مثل طرق @Insert، تقبل طرق @Update مثيلات كيان البيانات كمَعلمات. يُظهر الرمز التالي مثالاً على طريقة @Update تحاول تعديل كائن User واحد أو أكثر في قاعدة البيانات:

Kotlin

@Dao
interface UserDao {
    @Update
    fun updateUsers(vararg users: User)
}

Java

@Dao
public interface UserDao {
    @Update
    public void updateUsers(User... users);
}

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

يمكن أن تعرض طريقة @Update اختياريًا قيمة int التي تشير إلى عدد الصفوف التي تم تعديلها بنجاح.

حذف

يتيح لك التعليق التوضيحي @Delete تحديد الطرق التي تحذف صفوفًا معيّنة من جدول قاعدة بيانات. مثل طرق @Insert، تقبل طرق @Delete مثيلات كيان البيانات كمَعلمات. يعرض الرمز التالي مثالاً على طريقة @Delete تحاول حذف عنصر User واحد أو أكثر من قاعدة البيانات:

Kotlin

@Dao
interface UserDao {
    @Delete
    fun deleteUsers(vararg users: User)
}

Java

@Dao
public interface UserDao {
    @Delete
    public void deleteUsers(User... users);
}

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

يمكن أن تعرض طريقة @Delete اختياريًا قيمة int التي تشير إلى عدد الصفوف التي تم حذفها بنجاح.

طرق طلب البحث

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

تتحقّق الغرفة من استعلامات SQL في وقت التجميع. وهذا يعني أنه في حالة وجود مشكلة في الاستعلام الخاص بك، يحدث خطأ في التحويل البرمجي بدلاً من فشل وقت التشغيل.

طلبات بحث بسيطة

تحدّد الرمز البرمجي التالي طريقة تستخدم استعلام SELECT بسيط لعرض جميع كائنات User في قاعدة البيانات:

Kotlin

@Query("SELECT * FROM user")
fun loadAllUsers(): Array<User>

Java

@Query("SELECT * FROM user")
public User[] loadAllUsers();

توضح الأقسام التالية كيفية تعديل هذا المثال لحالات الاستخدام العادية.

إرجاع مجموعة فرعية من أعمدة الجدول

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

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

Kotlin

data class NameTuple(
    @ColumnInfo(name = "first_name") val firstName: String?,
    @ColumnInfo(name = "last_name") val lastName: String?
)

Java

public class NameTuple {
    @ColumnInfo(name = "first_name")
    public String firstName;

    @ColumnInfo(name = "last_name")
    @NonNull
    public String lastName;
}

بعد ذلك، يمكنك عرض هذا الكائن البسيط من طريقة الاستعلام الخاصة بك:

Kotlin

@Query("SELECT first_name, last_name FROM user")
fun loadFullName(): List<NameTuple>

Java

@Query("SELECT first_name, last_name FROM user")
public List<NameTuple> loadFullName();

تدرك الغرفة أن طلب البحث يعرض قيم للعمودين first_name وlast_name وأنه يمكن ربط هذه القيم بالحقول في الفئة NameTuple. إذا عرض طلب البحث عمودًا لم يتم ربطه بحقل في الكائن المعروض، فستعرض الغرفة تحذيرًا.

تمرير معلَمات بسيطة إلى طلب بحث

في معظم الأحيان، تحتاج طرق DAO الخاصة بك إلى قبول المعلمات حتى تتمكن من إجراء عمليات التصفية. تتيح الغرفة استخدام معلمات الطريقة كمعلمات ربط في طلبات البحث.

على سبيل المثال، تحدد التعليمة البرمجية التالية طريقة تعرض جميع المستخدمين الذين تجاوزوا فئة عمرية معينة:

Kotlin

@Query("SELECT * FROM user WHERE age > :minAge")
fun loadAllUsersOlderThan(minAge: Int): Array<User>

Java

@Query("SELECT * FROM user WHERE age > :minAge")
public User[] loadAllUsersOlderThan(int minAge);

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

Kotlin

@Query("SELECT * FROM user WHERE age BETWEEN :minAge AND :maxAge")
fun loadAllUsersBetweenAges(minAge: Int, maxAge: Int): Array<User>

@Query("SELECT * FROM user WHERE first_name LIKE :search " +
       "OR last_name LIKE :search")
fun findUserWithName(search: String): List<User>

Java

@Query("SELECT * FROM user WHERE age BETWEEN :minAge AND :maxAge")
public User[] loadAllUsersBetweenAges(int minAge, int maxAge);

@Query("SELECT * FROM user WHERE first_name LIKE :search " +
       "OR last_name LIKE :search")
public List<User> findUserWithName(String search);

تمرير مجموعة من المعلَمات إلى طلب بحث

قد تتطلب منك بعض طرق DAO تمرير عدد متغير من المعلمات غير معروف حتى وقت التشغيل. تدرك الغرفة الحالات التي تمثل فيها المعلمة مجموعة، ثم توسّعها تلقائيًا في وقت التشغيل بناءً على عدد المعلمات المقدمة.

على سبيل المثال، تحدّد الرمز البرمجي التالي طريقة تعرض معلومات حول جميع المستخدمين من مجموعة فرعية من المناطق:

Kotlin

@Query("SELECT * FROM user WHERE region IN (:regions)")
fun loadUsersFromRegions(regions: List<String>): List<User>

Java

@Query("SELECT * FROM user WHERE region IN (:regions)")
public List<User> loadUsersFromRegions(List<String> regions);

إجراء طلبات بحث في جداول متعددة

قد تتطلب بعض الاستعلامات الوصول إلى جداول متعددة لحساب النتيجة. يمكنك استخدام عبارات JOIN في استعلامات SQL للإشارة إلى أكثر من جدول واحد.

تحدد التعليمة البرمجية التالية طريقة تجمع ثلاثة جداول معًا لإعادة الكتب قيد القرض حاليًا إلى مستخدم معين:

Kotlin

@Query(
    "SELECT * FROM book " +
    "INNER JOIN loan ON loan.book_id = book.id " +
    "INNER JOIN user ON user.id = loan.user_id " +
    "WHERE user.name LIKE :userName"
)
fun findBooksBorrowedByNameSync(userName: String): List<Book>

Java

@Query("SELECT * FROM book " +
       "INNER JOIN loan ON loan.book_id = book.id " +
       "INNER JOIN user ON user.id = loan.user_id " +
       "WHERE user.name LIKE :userName")
public List<Book> findBooksBorrowedByNameSync(String userName);

يمكنك أيضًا تعريف كائنات بسيطة لعرض مجموعة فرعية من الأعمدة من جداول مرتبطة متعددة، كما هو موضَّح في القسم عرض مجموعة فرعية من أعمدة الجدول. يحدد الكود التالي DAO بطريقة تعرض أسماء المستخدمين وأسماء الكتب التي استعاروها:

Kotlin

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

    // You can also define this class in a separate file.
    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();

   // You can also define this class in a separate file, as long as you add the
   // "public" access modifier.
   static class UserBook {
       public String userName;
       public String bookName;
   }
}

إرجاع خريطة متعددة

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

بالنظر إلى المثال من قسم إجراء طلبات بحث في جداول متعددة. بدلاً من عرض قائمة بمثيلات فئة بيانات مخصّصة تتضمّن أزواج User وBook، يمكنك عرض تعيين 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();

عندما تعرض طريقة طلب البحث خريطة متعددة، يمكنك كتابة طلبات بحث تستخدم عبارات GROUP BY، ما يتيح لك الاستفادة من إمكانات SQL في العمليات الحسابية والفلترة المتقدمة. على سبيل المثال، يمكنك تعديل طريقة loadUserAndBookNames() لعرض المستخدمين الذين لديهم ثلاثة كتب أو أكثر تم التحقق منها فقط:

Kotlin

@Query(
    "SELECT * FROM user" +
    "JOIN book ON user.id = book.user_id" +
    "GROUP BY user.name WHERE COUNT(book.id) >= 3"
)
fun loadUserAndBookNames(): Map<User, List<Book>>

Java

@Query(
    "SELECT * FROM user" +
    "JOIN book ON user.id = book.user_id" +
    "GROUP BY user.name WHERE COUNT(book.id) >= 3"
)
public Map<User, List<Book>> loadUserAndBookNames();

إذا لم تكن بحاجة إلى ربط كائنات كاملة، يمكنك أيضًا عرض التعيينات بين أعمدة معيّنة في طلب البحث من خلال ضبط السمتَين keyColumn وvalueColumn في تعليق @MapInfo على طريقة طلب البحث:

Kotlin

@MapInfo(keyColumn = "userName", valueColumn = "bookName")
@Query(
    "SELECT user.name AS username, book.name AS bookname FROM user" +
    "JOIN book ON user.id = book.user_id"
)
fun loadUserAndBookNames(): Map<String, List<String>>

Java

@MapInfo(keyColumn = "userName", valueColumn = "bookName")
@Query(
    "SELECT user.name AS username, book.name AS bookname FROM user" +
    "JOIN book ON user.id = book.user_id"
)
public Map<String, List<String>> loadUserAndBookNames();

أنواع خاصة من الإرجاع

توفر غرفة بعض أنواع الإرجاع الخاصة لدمجها مع مكتبات واجهة برمجة التطبيقات الأخرى.

طلبات البحث المقسّمة على صفحات باستخدام "مكتبة تسجيل الصفحات"

تتيح الغرفة طلبات البحث المقسّمة على صفحات من خلال الدمج مع مكتبة تقسيم الصفحات. في الغرفة 2.3.0-alpha01 والإصدارات الأحدث، يمكن أن تعرض DAO عناصر PagingSource لاستخدامها مع نقل الصفحات 3.

Kotlin

@Dao
interface UserDao {
  @Query("SELECT * FROM users WHERE label LIKE :query")
  fun pagingSource(query: String): PagingSource<Int, User>
}

Java

@Dao
interface UserDao {
  @Query("SELECT * FROM users WHERE label LIKE :query")
  PagingSource<Integer, User> pagingSource(String query);
}

لمزيد من المعلومات عن اختيار مَعلمات نوع PagingSource، اطّلِع على القسم اختيار أنواع المفاتيح والقيمة.

الوصول المباشر للمؤشر

إذا كان منطق تطبيقك يتطلّب الوصول المباشر إلى الصفوف المعروضة، يمكنك كتابة طرق DAO لإرجاع عنصر Cursor، كما هو موضّح في المثال التالي:

Kotlin

@Dao
interface UserDao {
    @Query("SELECT * FROM user WHERE age > :minAge LIMIT 5")
    fun loadRawUsersOlderThan(minAge: Int): Cursor
}

Java

@Dao
public interface UserDao {
    @Query("SELECT * FROM user WHERE age > :minAge LIMIT 5")
    public Cursor loadRawUsersOlderThan(int minAge);
}

مراجع إضافية

لمعرفة المزيد من المعلومات حول الوصول إلى البيانات باستخدام أوامر DAO للغرف، اطّلع على الموارد الإضافية التالية:

عيّنات

الدروس التطبيقية حول الترميز