溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Android中如何使用Room架構(gòu)組件

發(fā)布時(shí)間:2021-07-12 11:42:58 來源:億速云 閱讀:178 作者:Leah 欄目:移動(dòng)開發(fā)

這篇文章將為大家詳細(xì)講解有關(guān)Android中如何使用Room架構(gòu)組件,文章內(nèi)容質(zhì)量較高,因此小編分享給大家做個(gè)參考,希望大家閱讀完這篇文章后對(duì)相關(guān)知識(shí)有一定的了解。

一、簡(jiǎn)介

Room是Google推出的Android架構(gòu)組件庫中的數(shù)據(jù)持久化組件庫, 也可以說是在SQLite上實(shí)現(xiàn)的一套ORM解決方案。

Room主要包含三個(gè)部分:

  • Database : 持有DB和DAO

  • Entity : 定義POJO類,即數(shù)據(jù)表結(jié)構(gòu)

  • DAO(Data Access Objects) : 定義訪問數(shù)據(jù)(增刪改查)的接口

其關(guān)系如下圖所示:

Android中如何使用Room架構(gòu)組件

Room Architecture Diagram

二、基本使用

1. 創(chuàng)建Entity

1.1 一個(gè)簡(jiǎn)單的Entitiy

一個(gè)簡(jiǎn)單Entity定義如下:

@Entity(tableName = "user" 
  indices = {@Index(value = {"first_name", "last_name"})})
public class User {
 @PrimaryKey
 private int uid;
 @ColumnInfo(name = "first_name")
 private String firstName;
 @ColumnInfo(name = "last_name")
 private String lastName;
 @Ignore
 public User(String firstName, String lastName) {
 this.uid = UUID.randomUUID().toString();
 this.firstName = firstName;
 this. lastName = lastName;
 }
 public User(String id, String firstName, String lastName) {
 this.uid = id;
 this.firstName = userName;
 this. lastName = userName;
 }
 // Getters and setters
}
  • @Entity(tableName = "table_name**") 注解POJO類,定義數(shù)據(jù)表名稱;

  • @PrimaryKey 定義主鍵,如果一個(gè)Entity使用的是復(fù)合主鍵,可以通過@Entity注解的primaryKeys 屬性定義復(fù)合主鍵:@Entity(primaryKeys = {"firstName", "lastName"})

  • @ColumnInfo(name = “column_name”) 定義數(shù)據(jù)表中的字段名

  • @Ignore 用于告訴Room需要忽略的字段或方法

  • 建立索引:在@Entity注解的indices屬性中添加索引字段。例如:indices = {@Index(value = {"first_name", "last_name"}, unique = true), ...}, unique = true可以確保表中不會(huì)出現(xiàn){"first_name", "last_name"} 相同的數(shù)據(jù)。

1.2 Entitiy間的關(guān)系

不同于目前存在的大多數(shù)ORM庫,Room不支持Entitiy對(duì)象間的直接引用。

但Room允許通過外鍵(Foreign Key)來表示Entity之間的關(guān)系。

@Entity(foreignKeys = @ForeignKey(entity = User.class,
     parentColumns = "id",
     childColumns = "user_id"))
class Book {
 @PrimaryKey
 public int bookId;
 public String title;
 @ColumnInfo(name = "user_id")
 public int userId;
}

如上面代碼所示,Book對(duì)象與User對(duì)象是屬于的關(guān)系。Book中的user_id,對(duì)應(yīng)User中的id。 那么當(dāng)一個(gè)User對(duì)象被刪除時(shí), 對(duì)應(yīng)的Book會(huì)發(fā)生什么呢?

@ForeignKey注解中有兩個(gè)屬性onDelete和onUpdate, 這兩個(gè)屬性對(duì)應(yīng)ForeignKey中的onDelete()和onUpdate(), 通過這兩個(gè)屬性的值來設(shè)置當(dāng)User對(duì)象被刪除/更新時(shí),Book對(duì)象作出的響應(yīng)。這兩個(gè)屬性的可選值如下:

  • CASCADE:User刪除時(shí)對(duì)應(yīng)Book一同刪除; 更新時(shí),關(guān)聯(lián)的字段一同更新

  • NO_ACTION:User刪除時(shí)不做任何響應(yīng)

  • RESTRICT:禁止User的刪除/更新。當(dāng)User刪除或更新時(shí),Sqlite會(huì)立馬報(bào)錯(cuò)。

  • SET_NULL:當(dāng)User刪除時(shí), Book中的userId會(huì)設(shè)為NULL

  • SET_DEFAULT:與SET_NULL類似,當(dāng)User刪除時(shí),Book中的userId會(huì)設(shè)為默認(rèn)值

1.3 對(duì)象嵌套

在某些情況下, 對(duì)于一張表中的數(shù)據(jù)我們會(huì)用多個(gè)POJO類來表示,在這種情況下可以用@Embedded注解嵌套的對(duì)象,比如:

class Address {
 public String street;
 public String state;
 public String city;
 @ColumnInfo(name = "post_code")
 public int postCode;
}
@Entity
class User {
 @PrimaryKey
 public int id;
 public String firstName;
 @Embedded
 public Address address;
}

以上代碼所產(chǎn)生的User表中,Column 為id, firstName, street, state, city, post_code

2. 創(chuàng)建數(shù)據(jù)訪問對(duì)象(DAO)

@Dao
public interface UserDao {
 @Query("SELECT * FROM user")
 List<User> getAll();
 @Query("SELECT * FROM user WHERE uid IN (:userIds)")
 List<User> loadAllByIds(int[] userIds);
 @Query("SELECT * FROM user WHERE first_name LIKE :first AND "
  + "last_name LIKE :last LIMIT 1")
 User findByName(String first, String last);
 @Insert
 void insertAll(List<User> users);
 @Insert(onConflict = OnConflictStrategy.REPLACE)
 public void insertUsers(User... users);
 @Delete
 void delete(User user);
 @Update
 public void updateUsers(List<User> users);
}

DAO 可以是一個(gè)接口,也可以是一個(gè)抽象類, Room會(huì)在編譯時(shí)創(chuàng)建DAO的實(shí)現(xiàn)。

Tips:

  • @Insert方法也可以定義返回值, 當(dāng)傳入?yún)?shù)僅有一個(gè)時(shí)返回long, 傳入多個(gè)時(shí)返回long[]或List<Long>, Room在實(shí)現(xiàn)insert方法的實(shí)現(xiàn)時(shí)會(huì)在一個(gè)事務(wù)進(jìn)行所有參數(shù)的插入。

  • @Insert的參數(shù)存在沖突時(shí), 可以設(shè)置onConflict屬性的值來定義沖突的解決策略, 比如代碼中定義的是@Insert(onConflict = OnConflictStrategy.REPLACE), 即發(fā)生沖突時(shí)替換原有數(shù)據(jù)

  • @Update和@Delete 可以定義int類型返回值,指更新/刪除的函數(shù)

DAO中的增刪改方法的定義都比較簡(jiǎn)單,這里不展開討論,下面更多的聊一下查詢方法。

2.1 簡(jiǎn)單的查詢

Talk is cheap, 直接show code:

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

Room會(huì)在編譯時(shí)校驗(yàn)sql語句,如果@Query() 中的sql語句存在語法錯(cuò)誤,或者查詢的表不存在,Room會(huì)在編譯時(shí)報(bào)錯(cuò)。

2.2 查詢參數(shù)傳遞

@Query("SELECT * FROM user WHERE uid IN (:userIds)")
List<User> loadAllByIds(int[] userIds);
@Query("SELECT * FROM user WHERE first_name LIKE :first AND "
  + "last_name LIKE :last LIMIT 1")
User findByName(String first, String last);

看代碼應(yīng)該比較好理解, 方法中傳遞參數(shù)arg, 在sql語句中用:arg即可。編譯時(shí)Room會(huì)匹配對(duì)應(yīng)的參數(shù)。

如果在傳參中沒有匹配到:arg對(duì)應(yīng)的參數(shù), Room會(huì)在編譯時(shí)報(bào)錯(cuò)。

2.3 查詢表中部分字段的信息

在實(shí)際某個(gè)業(yè)務(wù)場(chǎng)景中, 我們可能僅關(guān)心一個(gè)表部分字段的值,這時(shí)我僅需要查詢關(guān)心的列即可。

定義子集的POJO類:

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

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

在DAO中添加查詢方法:

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

這里定義的POJO也支持使用@Embedded

2.3 查詢結(jié)果的返回類型

Room中查詢操作除了返回POJO對(duì)象及其List以外, 還支持:

LiveData<T>:
LiveData是架構(gòu)組件庫中提供的另一個(gè)組件,可以很好滿足數(shù)據(jù)變化驅(qū)動(dòng)UI刷新的需求。Room會(huì)實(shí)現(xiàn)更新LiveData的代碼。

@Query("SELECT first_name, last_name FROM user WHERE region IN (:regions)") 
public LiveData<List<User>> loadUsersFromRegionsSync(List<String> regions);
Flowablbe<T> Maybe<T> Single<T>:
Room 支持返回RxJava2 的Flowablbe, Maybe和Single對(duì)象,對(duì)于使用RxJava的項(xiàng)目可以很好的銜接, 但需要在gradle添加該依賴:android.arch.persistence.room:rxjava2。
@Query("SELECT * from user where id = :id LIMIT 1")
public Flowable<User> loadUserById(int id);

Cursor:

返回Cursor是為了支持現(xiàn)有項(xiàng)目中使用Cursor的場(chǎng)景,官方不建議直接返回Cursor.

Caution: It's highly discouraged to work with the Cursor API because it doesn't guarantee whether the rows exist or what values the rows contain. Use this functionality only if you already have code that expects a cursor and that you can't refactor easily.

2.4 聯(lián)表查詢

Room支持聯(lián)表查詢,接口定義上與其他查詢差別不大, 主要還是sql語句的差別。

@Dao
public interface MyDao {
 @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);
}

3. 創(chuàng)建數(shù)據(jù)庫

Room中DataBase類似SQLite API中SQLiteOpenHelper,是提供DB操作的切入點(diǎn),但是除了持有DB外, 它還負(fù)責(zé)持有相關(guān)數(shù)據(jù)表(Entity)的數(shù)據(jù)訪問對(duì)象(DAO), 所以Room中定義Database需要滿足三個(gè)條件:

  • 繼承RoomDataBase,并且是一個(gè)抽象類

  • 用@Database 注解,并定義相關(guān)的entity對(duì)象, 當(dāng)然還有必不可少的數(shù)據(jù)庫版本信息

  • 定義返回DAO對(duì)象的抽象方法

@Database(entities = {User.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {
 public abstract UserDao userDao();
}

創(chuàng)建好以上Room的三大組件后, 在代碼中就可以通過以下代碼創(chuàng)建Database實(shí)例。

AppDatabase db = Room.databaseBuilder(getApplicationContext(),
 AppDatabase.class, "database-name").build();

三、數(shù)據(jù)庫遷移

3.1 Room數(shù)據(jù)庫升級(jí)

在傳統(tǒng)的SQLite API中,我們?nèi)绻?jí)數(shù)據(jù)庫, 通常在SQLiteOpenHelper.onUpgrade方法執(zhí)行數(shù)據(jù)庫升級(jí)的sql語句,這些sql語句的通常根據(jù)數(shù)據(jù)庫版本以文件的方式或者用數(shù)組來管理。有人說這種方式升級(jí)數(shù)據(jù)庫就像在拆炸彈,相比之下在Room中升級(jí)數(shù)據(jù)庫簡(jiǎn)單的就像是按一個(gè)開關(guān)而已。

Room提供了Migration類來實(shí)現(xiàn)數(shù)據(jù)庫的升級(jí):

Room.databaseBuilder(getApplicationContext(), MyDb.class, "database-name")
 .addMigrations(MIGRATION_1_2, MIGRATION_2_3).build();
static final Migration MIGRATION_1_2 = new Migration(1, 2) {
 @Override
 public void migrate(SupportSQLiteDatabase database) {
 database.execSQL("CREATE TABLE `Fruit` (`id` INTEGER, "
  + "`name` TEXT, PRIMARY KEY(`id`))");
 }
};
static final Migration MIGRATION_2_3 = new Migration(2, 3) {
 @Override
 public void migrate(SupportSQLiteDatabase database) {
 database.execSQL("ALTER TABLE Book "
  + " ADD COLUMN pub_year INTEGER");
 }
};

在創(chuàng)建Migration類時(shí)需要指定startVersion和endVersion, 代碼中MIGRATION_1_2和MIGRATION_2_3的startVersion和endVersion是遞增的, Migration其實(shí)是支持從版本1直接升到版本3,只要其migrate()方法里執(zhí)行的語句正常即可。那么Room是怎么實(shí)現(xiàn)數(shù)據(jù)庫升級(jí)的呢?其實(shí)本質(zhì)上還是調(diào)用SQLiteOpenHelper.onUpgrade,Room中自己實(shí)現(xiàn)了一個(gè)SQLiteOpenHelper, 在onUpgrade()方法被調(diào)用時(shí)觸發(fā)Migration,當(dāng)?shù)谝淮卧L問數(shù)據(jù)庫時(shí),Room做了以下幾件事:

  • 創(chuàng)建Room Database實(shí)例

  • SQLiteOpenHelper.onUpgrade被調(diào)用,并且觸發(fā)Migration

  • 打開數(shù)據(jù)庫

這樣一看, Room中處理數(shù)據(jù)庫升級(jí)確實(shí)很像是加一個(gè)開關(guān)。

3.2 原有SQLite數(shù)據(jù)庫遷移至Room

因?yàn)镽oom使用的也是SQLite, 所以可以很好的支持原有Sqlite數(shù)據(jù)庫遷移到Room。

假設(shè)原有一個(gè)版本號(hào)為1的數(shù)據(jù)庫有一張表User, 現(xiàn)在要遷移到Room, 我們需要定義好Entity, DAO, Database, 然后創(chuàng)建Database時(shí)添加一個(gè)空實(shí)現(xiàn)的Migraton即可。需要注意的是,即使對(duì)數(shù)據(jù)庫沒有任何升級(jí)操作,也需要升級(jí)版本, 否則會(huì)拋異常IllegalStateException.

@Database(entities = {User.class}, version = 2)
public abstract class UsersDatabase extends RoomDatabase {
…
static final Migration MIGRATION_1_2 = new Migration(1, 2) {
 @Override
 public void migrate(SupportSQLiteDatabase database) {
 // Since we didn't alter the table, there's nothing else to do here.
 }
};
…
database = Room.databaseBuilder(context.getApplicationContext(),
 UsersDatabase.class, "Sample.db")
 .addMigrations(MIGRATION_1_2)
 .build();

四、復(fù)雜數(shù)據(jù)的處理

在某些場(chǎng)景下我們的應(yīng)用可能需要存儲(chǔ)復(fù)雜的數(shù)據(jù)類型,比如Date,但是Room的Entity僅支持基本數(shù)據(jù)類型和其裝箱類之間的轉(zhuǎn)換,不支持其它的對(duì)象引用。所以Room提供了TypeConverter給使用者自己實(shí)現(xiàn)對(duì)應(yīng)的轉(zhuǎn)換。

一個(gè)Date類型的轉(zhuǎn)換如下:

public class Converters {
 @TypeConverter
 public static Date fromTimestamp(Long value) {
 return value == null ? null : new Date(value);
 }
 @TypeConverter
 public static Long dateToTimestamp(Date date) {
 return date == null ? null : date.getTime();
 }
}

定義好轉(zhuǎn)換方法后,指定到對(duì)應(yīng)的Database上即可, 這樣就可以在對(duì)應(yīng)的POJO(User)中使用Date類了。

@Database(entities = {User.class}, version = 1)
@TypeConverters({Converters.class})
public abstract class AppDatabase extends RoomDatabase {
 public abstract UserDao userDao();
}
@Entity
public class User {
 ...
 private Date birthday;
}

五、總結(jié)

在SQLite API方式實(shí)現(xiàn)數(shù)據(jù)持久化的項(xiàng)目中,相信都有一個(gè)任務(wù)繁重的SQLiteOpenHelper實(shí)現(xiàn), 一堆維護(hù)表的字段的Constant類, 一堆代碼類似的數(shù)據(jù)庫訪問類(DAO),訪問數(shù)據(jù)庫時(shí)需要做Cursor的遍歷,構(gòu)建并返回對(duì)應(yīng)的POJO類...相比之下,Room作為在SQLite之上封裝的ORM庫確實(shí)有諸多優(yōu)勢(shì),比較直觀的體驗(yàn)是:

  • 比SQLite API更簡(jiǎn)單的使用方式

  • 省略了許多重復(fù)代碼

  • 能在編譯時(shí)校驗(yàn)sql語句的正確性

  • 數(shù)據(jù)庫相關(guān)的代碼分為Entity, DAO, Database三個(gè)部分,結(jié)構(gòu)清晰

  • 簡(jiǎn)單安全的數(shù)據(jù)庫升級(jí)方案


關(guān)于Android中如何使用Room架構(gòu)組件就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,可以學(xué)到更多知識(shí)。如果覺得文章不錯(cuò),可以把它分享出去讓更多的人看到。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI