تهیه پیش‌فروش دوره «آموزش تکمیلی و پروژه محور Spring Boot» با ۳۵% تخفیف - فقط تا ۱۳ شهریور 

بررسی روابط در Android Architecture Components: Room

پنج شنبه ۳۰ فروردین ۹۷ توسط یوسف رضا مختاری

در مقاله قبلی ما به معرفی کتابخانه Room از Android Architecture Components پرداختیم .

امروز میخواهیم با ایجاد روابط در پایگاه داده با استفاده از Room آشنا شویم . بیایید شروع کنیم !

معرفی

در پایگاه داده SQLite ما مجاز به مشخص کردن روابط بین  object ها هستیم ، بنابراین می توانیم یک یا چند object را به یک یا چند object دیگر مرتبط کنیم .

این ارتباط یک به چند یا ارتباط چند به چند می باشد .

به عنوان مثال ، ما می توانیم کاربر (User) را در پایگاه داده داشته باشیم.

یک کاربر می تواند مخزن (Repository) های بسیاری داشته باشد . این ارتباط یک به چند می باشد.

اما از طرف دیگر یک مخزن هم میتواند کاربران بسیاری داشته باشد.

بنابراین هر کاربر می توانید چند مخزن داشته باشد و هر مخزن هم میتواند چند کاربر داشته باشد.

در این مورد ارتباط چند به چند می باشد .

 

ارتباط یک به چند

اجازه دهید از مثال مطلب قبلی استفاده کنیم و  فرض کنیم یک کاربر مخزن های بسیاری دارد.

در این سناریو ما دو موجودیت داریم به اسم User و Repo .

در ابتدا ما نیاز به ایجاد connection بین این دو موجودیت داریم و سپس ما قادر به گرفتن اطلاعات مناسب از پایگاه داده هستیم.

در مرحله اول ما نیاز داریم یک موجودیت برای User تعریف کنیم.

import android.arch.persistence.room.Entity;
import android.arch.persistence.room.PrimaryKey;

@Entity
public class User {
    @PrimaryKey public final int id;
    public final String login;
    public final String avatarUrl;

    public User(int id, String login, String avatarUrl) {
        this.id = id;
        this.login = login;
        this.avatarUrl = avatarUrl;
    }
}

 

اگر نمیدانید annotation یا حاشیه نویسیEntity@ و PrimaryKey@ برای چیست به پست قبلی رجوع کنید .

برای مدل مخزن , ما از کلاس Repo پست قبلی با یک تغییر اما مهم , استفاده میکنیم .

import android.arch.persistence.room.Entity;
import android.arch.persistence.room.ForeignKey;
import android.arch.persistence.room.PrimaryKey;

import static android.arch.persistence.room.ForeignKey.CASCADE;

@Entity(foreignKeys = @ForeignKey(entity = User.class,
                                  parentColumns = "id",
                                  childColumns = "userId",
                                  onDelete = CASCADE))
public class Repo {
    @PrimaryKey public final int id;
    public final String name;
    public final String url;
    public final int userId;

    public Repo(final int id, String name, String url,
    final int userId) {
        this.id = id;
        this.name = name;
        this.url = url;
        this.userId = userId;
    }
}

 

اگر این مدل Repo را با پست قبلی مقایسه کنید ، دو تفاوت را خواهید دید:

  • پارامتر foreignKeys یا کلیدهای خارجی در حاشیه نویسی Entity@
  • اضافه شدن یک فیلد برای userId

اضافه کردن foreignKeys به معنی ایجاد connection بین این موجودیت و برخی کلاس های دیگر است.

در این پارامتر ما , parentColumns را که  ستون id از کلاس User

و childColumns را که ستون user id از کلاس Repo است را مشخص میکنیم .

ایجاد connection برای ارتباط(relation) ضروری نیست اما به شما کمک میکند تا تعریف کنید در در صورت حذف یا بروزرسانی سطر User چه اتفاقی در سطر Repo افتد .

و آخرین پارامتر برای :  onDelete = CASCADE

به طور مشخص می گوییم که اگر سطری از user حذف شود، ما می خواهیم همه مخزن آن را نیز حذف کنیم .

شما همچنین می توانید با استفاده از پارامتر onUpdate = CASCADE راه حلی مشابه تعریف کنید .

شما می توانید در مورد سایر راه حل های ممکن در مستندات ForeignKey بخوانید .

در حال حاضر، پس از آماده سازی مدل ها ، ما باید یک SQL query مناسب برای انتخاب مخازن یک کاربر خاص ایجاد کنیم.

 

SQL query

DAO ها بهترین مکان برای ایجاد دستورات SQL ما هستند و RepoDao ما ممکن است این چنین باشد:

import android.arch.persistence.room.Dao;
import android.arch.persistence.room.Delete;
import android.arch.persistence.room.Insert;
import android.arch.persistence.room.Query;
import android.arch.persistence.room.Update;
import java.util.List;

@Dao
public interface RepoDao {

    @Insert
    void insert(Repo repo);

    @Update
    void update(Repo... repos);

    @Delete
    void delete(Repo... repos);

    @Query("SELECT * FROM repo")
    List getAllRepos();    

    @Query("SELECT * FROM repo WHERE userId=:userId")
    List findRepositoriesForUser(final int userId);
}

در بالا ما سه متد برای insert , update و delete  داریم (شما می توانید برای مطالعه بیشتر به پست قبلی رجوع کنید)

و دو روش برای گرفتن اطلاعات — در روش اول تمام مخازن و در روش دوم گرفتن مخازن یک کاربر خاص

ما هم چنین نیاز داریم که UserDao را  همراه با متد های insert  , update ,  reomove  ایجاد کنیم .

 

import android.arch.persistence.room.Dao;
import android.arch.persistence.room.Delete;
import android.arch.persistence.room.Insert;
import android.arch.persistence.room.Update;

@Dao
public interface UserDao {

    @Insert
    void insert(User... user);

    @Update
    void update(User... user);

    @Delete
    void delete(User... user);
}

 

کلاس پایگاه داده ما ، RepoDatabase، هم نیاز به بروزرسانی دارد که  با اضافه کردن کلاس های مدل مناسب در حاشیه نویسی  Datatabase@ و اضافه کردن abstract method برای دریافت UserDao:

@Database(entities = { Repo.class, User.class },
          version = 1)
public abstract class RepoDatabase extends RoomDatabase {

    ...    

    public abstract RepoDao getRepoDao();
    public abstract UserDao getUserDao();
}

 

و همین . الان میتوانیم از پایگاه داده برای insert کردن کاربران و مخازن استفاده کنیم :

RepoDao repoDao = RepoDatabase
        .getInstance(context)
        .getRepoDao();

UserDao userDao = RepoDatabase
        .getInstance(context)
        .getUserDao();

userDao.insert(new User(1,
        "Jake Wharton",
        "https://avatars0.githubusercontent.com/u/66577"));

repoDao.insert(new Repo(1, 
        "square/retrofit", 
        "https://github.com/square/retrofit", 
        1));

List repositoriesForUser = repoDao.
        findRepositoriesForUser(1);

 

ارتباط چند به چند

در رابطه چند به چند (یا  N به M)   نیاز به داشتن یک جدول join با کلید های خارجی (foreign keys) به دیگر موجودیت ها می باشد.

ما می توانیم از مثال بخش قبلی با کمی تغییر استفاده کنیم .

بنابراین اکنون نه تنها هر کاربر می تواند مخازن بسیاری داشته باشد، بلکه هر مخزن می تواند به بسیاری از کاربران تعلق داشته باشد !

برای انجام این کار، ما به ساده ترین نسخه از مدل خود برمی گردیم :

import android.arch.persistence.room.Entity;
import android.arch.persistence.room.PrimaryKey;

@Entity
public class User {
    @PrimaryKey
    public final int id;
    public final String login;
    public final String avatarUrl;

    public User(int id, String login, String avatarUrl) {
        this.id = id;
        this.login = login;
        this.avatarUrl = avatarUrl;
    }
}

و همینطور برای مدل Repo :

import android.arch.persistence.room.Entity;
import android.arch.persistence.room.PrimaryKey;

@Entity
public class Repo {
    @PrimaryKey 
    public final int id;
    public final String name;
    public final String url;

    public Repo(int id, String name, String url) {
        this.id = id;
        this.name = name;
        this.url = url;
    }
}

در مرحله بعد ما جدول join را ایجاد خواهیم کرد - کلاس  UserRepoJoin

import android.arch.persistence.room.Entity;
import android.arch.persistence.room.ForeignKey;

@Entity(tableName = "user_repo_join",
        primaryKeys = { "userId", "repoId" },
        foreignKeys = {
                @ForeignKey(entity = User.class,
                            parentColumns = "id",
                            childColumns = "userId"),
                @ForeignKey(entity = Repo.class,
                            parentColumns = "id",
                            childColumns = "repoId")
                })
public class UserRepoJoin {
    public final int userId;
    public final int repoId;

    public UserRepoJoin(final int userId, final int repoId) {
        this.userId = userId;
        this.repoId = repoId;
    }
}

 

در نگاه اول ممکن است به ظاهر ترسناک برسد، اما فرصتی دیگر به آن بدهید

اینجا چی داریم؟

  • پارامتر tableName برای ارائه یک نام خاص برای جدول مان .
  • پارامتر primaryKeys  برای داشتن چند primary key — در SQL ما می توانیم نه تنها یک primary key ، بلکه مجموعه ای از primary keyها را داشته باشیم! این کلید composite primary key نامیده می شود و برای این استفاده میشود تا مشخص کند برای هر سطر در جدول join باید هم userId و هم repoId منحصر بفرد باشند.
  • پارامتر foreignKey برای مشخص کردن کلید های خارجی به جداول دیگر است . در اینجا ما می گوییم که userId از جدول join ما، child id برای کلاس User است و به همین ترتیب برای مدل Repo .

 

حالا که ما کلید های خارجی را مشخص می کنیم، آماده ایجاد SQL مناسب برای join داخلی(inner join) هستیم:

 

import android.arch.persistence.room.Dao;
import android.arch.persistence.room.Insert;
import android.arch.persistence.room.Query;
import java.util.List;

@Dao
public interface UserRepoJoinDao {
    @Insert
    void insert(UserRepoJoin userRepoJoin);

    @Query("SELECT * FROM user INNER JOIN user_repo_join ON
           user.id=user_repo_join.userId WHERE
           user_repo_join.repoId=:repoId")
    List getUsersForRepository(final int repoId);

    @Query("SELECT * FROM repo INNER JOIN user_repo_join ON
           repo.id=user_repo_join.repoId WHERE
           user_repo_join.userId=:userId")
    List getRepositoriesForUsers(final int userId);
}

 

با این روش میتوانیم هم کاربران یک مخزن خاص و هم مخازن یک کاربر خاص را دریافت کنیم .

آخرین مرحله برای تغییر RepoDatabase :

@Database(entities = { Repo.class, User.class, UserRepoJoin.class },
          version = 1)
public abstract class RepoDatabase extends RoomDatabase {

    ...

    public abstract RepoDao getRepoDao();
    public abstract UserDao getUserDao();
    public abstract UserRepoJoinDao getUserRepoJoinDao();
}

و اکنون می توانیم کاربران و مخازن را به پایگاه داده وارد کنیم:

RepoDao repoDao = RepoDatabase
    .getInstance(context)
    .getRepoDao();

UserDao userDao = RepoDatabase
    .getInstance(context)
    .getUserDao();

UserRepoJoinDao userRepoJoinDao = RepoDatabase
    .getInstance(context)
    .getUserRepoJoinDao();

userDao.insert(new User(1,
        "Jake Wharton",
        "https://avatars0.githubusercontent.com/u/66577"));

repoDao.insert(new Repo(1, 
        "square/retrofit", 
        "https://github.com/square/retrofit"));

userRepoJoinDao.insert(new UserRepoJoin(1, 1));

 

استفاده از حاشیه نویسی Relation@

یکی دیگر راه های ارائه روابط با استفاده Room حاشیه نویسی Relation@ می باشد .

شما  میتوانید چنین رابطه ای را فقط در داخل کلاس غیرنهادی یا non-entity اعلام کنید. بیایید به این مثال نگاه کنیم :

@Entity
public class User {
    @PrimaryKey public final int id;
    public final String login;
    public final String avatarUrl;

    public User(int id, String login, String avatarUrl) {
        this.id = id;
        this.login = login;
        this.avatarUrl = avatarUrl;
    }
}

@Entity
public class Repo {
    @PrimaryKey public final int id;
    public final String name;
    public final String url;
    public final int userId;

    public Repo(int id, String name, String url, int userId) {
        this.id = id;
        this.name = name;
        this.url = url;
        this.userId = userId;
    }
}

در بالا ما به سادگی کلاس های مدل خود را داریم —  User و Repo با فیلد userId

حالا ما باید یک کلاس مدل non-entity ایجاد کنیم:

public class UserWithRepos {
    @Embedded public User user;

    @Relation(parentColumn = "id",
              entityColumn = "userId") public List repoList;
}

در اینجا ما دو حاشیه نویسی جدید داریم:

  • Embedded@ برای داشتن فیلد های تودرتو  — به این ترتیب کلاس User ما در کلاس UserWithRepos تعبیه خواهد شد.
  • Relation@ برای داشتن رابطه با کلاس مدل دیگر است . این دو پارامتر می گویند که  parentColumn برابر با id  از کلاس User و entityColumn برابر با UserId از کلاس Repo می باشد.

 

به این ترتیب می توانیم از دستورات SQL مناسب در DAO برای انتخاب کاربران با تمام مخازنشان استفاده کنیم :

@Dao
public interface UserWithReposDao {

    @Query("SELECT * from user")
    public List getUsersWithRepos();

}

این ساده ترین راه است ، با این حال شما نمی توانید  اقدام به حذف یا بروز رسانی parent هایی  کنید که حاوی حاشیه نویسی ForeignKey@ باشد.

 

امیدوارم این مطلب برای شما مفید بوده باشد.

 


کلیدواژه: آموزش برنامه نویسی اندروید آموزش دیتابیس Room Room اندروید Android Architecture Components ایجاد روابط در پایگاه داده room

منابع: android.jlelse.eu

دیدگاه ها:
امید
۲ هفته قبل
reply
به نظر خیلی پیچیده تر میاد از ساخت DbHandler و این داستانا
ارسال دیدگاه:
برای ارسال دیگاه باید به سیستم وارد شوید و یا ثبت نام کنید. ثبت نام چند لحظه بیشتر زمان شما را نمیگیرد.