Android Room 使用詳解

一锌云、概述

Room提供了一個訪問SQLite的抽象層葬燎,以便在利用SQLite的全部功能的同時進(jìn)行流暢的數(shù)據(jù)庫訪問辫呻。

要在項目中使用Room需要在程序的build.gradle文件中添加以下依賴

dependencies {
    def room_version = "2.1.0-alpha03"

    implementation "androidx.room:room-runtime:$room_version"
    annotationProcessor "androidx.room:room-compiler:$room_version" // use kapt for Kotlin

    // optional - RxJava support for Room
    implementation "androidx.room:room-rxjava2:$room_version"

    // optional - Guava support for Room, including Optional and ListenableFuture
    implementation "androidx.room:room-guava:$room_version"

    // optional - Coroutines support for Room
    implementation "androidx.room:room-coroutines:$room_version"

    // Test helpers
    testImplementation "androidx.room:room-testing:$room_version"
}

Room主要有以下三個部分組成:

  1. Database:
    標(biāo)有 @Database注解的類需要具備以下特征:
    • 繼承RoomDatabase的抽象類
    • 在注釋中包括與數(shù)據(jù)庫關(guān)聯(lián)的實體列表(@Database(entities ={ }))
    • 包含一個無參的抽象方法并返回一個使用@Dao注解的類
  2. Entity:對應(yīng)數(shù)據(jù)庫中的表
  3. DAO:包含訪問數(shù)據(jù)庫的方法

以上各部分的依賴關(guān)系如下圖所示:

room_architecture.png

下面使用一個簡單的例子介紹各部分如何使用:
User

@Entity
public class User {
    @PrimaryKey
    public int uid;

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

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

UserDao

@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(User... users);

    @Delete
    void delete(User user);
}

AppDatabase

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

在創(chuàng)建完上面的文件之后埋同,可以使用以下代碼獲得數(shù)據(jù)庫的實例:

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

在實例化AppDatabase對象時劳较,應(yīng)遵循單例設(shè)計模式驹止,因為每個RoomDatabase實例都相當(dāng)消耗性能浩聋,并且您很少需要訪問多個實例。

二幢哨、 Entity定義數(shù)據(jù)

默認(rèn)情況下赡勘,Room為實體中定義的每個字段創(chuàng)建一個列。如果實體有不想持久的字段捞镰,則可以使用@Ignore來注解它們闸与。必須通過Database類中的entities數(shù)組引用實體類。
下面的代碼片段顯示了如何定義實體:

@Entity
public class User {
    @PrimaryKey
    public int id;

    public String firstName;
    public String lastName;
}

使用主鍵

每個實體必須定義至少1個字段作為主鍵岸售。即使只有1個字段践樱,仍然需要用@PrimaryKey注解字段。此外凸丸,如果您想Room自動分配IDs給實體拷邢,則可以設(shè)置@PrimaryKeyautoGenerate屬性。如果實體具有復(fù)合主鍵屎慢,則可以使用@Entity注解的primaryKeys屬性瞭稼,如下面的代碼片段所示:

@Entity(primaryKeys = {"firstName", "lastName"})
public class User {
    public String firstName;
    public String lastName;
}

默認(rèn)情況下,Room使用類名作為數(shù)據(jù)庫表名腻惠。如果希望表具有不同的名稱环肘,請設(shè)置@Entity注解的tableName屬性
SQLite中的表名不區(qū)分大小寫。
與tableName屬性類似集灌,Room使用字段名稱作為數(shù)據(jù)庫中的列名悔雹。如果希望列具有不同的名稱,請將@ColumnInfo注解添加到字段中欣喧,如下面的代碼片段所示:

@Entity(tableName = "users")
public class User {
    @PrimaryKey
    public int id;

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

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

索引

需要索引數(shù)據(jù)庫中的某些列以加快查詢速度腌零。若要向?qū)嶓w添加索引,在@Entity注釋中添加indices屬性 唆阿,下面的代碼片段演示了這個注解過程:

@Entity(indices = {@Index("name"),
        @Index(value = {"last_name", "address"})})
public class User {
    @PrimaryKey
    public int id;

    public String firstName;
    public String address;

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

    @Ignore
    Bitmap picture;
}

有時益涧,數(shù)據(jù)庫中的某些字段或字段組必須是唯一的⊙北睿可以通過將@Index注解的unique屬性設(shè)置為true來強制執(zhí)行此唯一性屬性饰躲。下面的代碼示例防止表中包含兩行,它們包含firstName和lastName列的相同值集:

@Entity(indices = {@Index(value = {"first_name", "last_name"},
        unique = true)})
public class User {
    @PrimaryKey
    public int id;

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

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

    @Ignore
    Bitmap picture;
}

三臼隔、Dao訪問數(shù)據(jù)

DAO既可以是接口,也可以是抽象類妄壶。如果是抽象類摔握,它可以有一個構(gòu)造函數(shù),它把RoomDatabase作為唯一的參數(shù)丁寄。Room在編譯時創(chuàng)建每個DAO實現(xiàn)氨淌。

Insert

當(dāng)您創(chuàng)建一個DAO方法并用@Insert注解時泊愧,Room生成一個實現(xiàn),在一個事務(wù)中將所有參數(shù)插入到數(shù)據(jù)庫中

@Dao
public interface MyDao {
    @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方法只接收1個參數(shù)盛正,則可以返回一個Long型的值删咱,這是插入項的新rowId。如果參數(shù)是數(shù)組或集合豪筝,則應(yīng)該返回long[] 或者 List類型的值痰滋。
有時插入數(shù)據(jù)和更新數(shù)據(jù)會產(chǎn)生沖突,所以就有了沖突之后要怎么解決,SQLite對于事務(wù)沖突定義了5個方案
OnConflictStrategy

  • REPLACE:見名知意,替換,違反的記錄被刪除,以新記錄代替之
  • ignore: 違反的記錄保持原貌续崖,其它記錄繼續(xù)執(zhí)行
  • fail: 終止命令敲街,違反之前執(zhí)行的操作得到保存
  • abort 終止命令,恢復(fù)違反之前執(zhí)行的修改
  • rollback 終止命令和事務(wù)严望,回滾整個事務(wù)

Update

@Update注解在數(shù)據(jù)庫中用于修改一組實體的字段多艇。它使用每個實體的主鍵來匹配查詢。

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

Delete

用于從數(shù)據(jù)庫中刪除給定參數(shù)的一系列實體像吻,它使用主鍵匹配數(shù)據(jù)庫中相應(yīng)的行

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

Query

@Query是DAO類中使用的主要注解峻黍。它允許您在數(shù)據(jù)庫上執(zhí)行讀/寫操作。每個@Query方法在編譯時被驗證拨匆,因此姆涩,如果存在查詢問題,則會發(fā)生編譯錯誤而不是運行時故障涮雷。

Room還驗證查詢的返回值阵面,這樣如果返回對象中字段的名稱與查詢響應(yīng)中的相應(yīng)列名不匹配,則Room將以以下兩種方式之一提醒您:

  • 如果只有一些字段名匹配洪鸭,則發(fā)出警告样刷。
  • 如果沒有字段名匹配,則會出錯览爵。

1置鼻、簡單查詢

@Dao
public interface MyDao {
    @Query("SELECT * FROM user")
    public User[] loadAllUsers();
}

2、將參數(shù)傳遞到查詢中

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

3蜓竹、傳遞參數(shù)集合
有些查詢可能要求您傳遞一個可變數(shù)量的參數(shù)箕母,其中參數(shù)的確切數(shù)目直到運行時才知道。例如俱济,您可能希望從區(qū)域的子集檢索有關(guān)所有用戶的信息

@Dao
public interface MyDao {
    @Query("SELECT first_name, last_name FROM user WHERE region IN (:regions)")
    public LiveData<List<User>> loadUsersFromRegionsSync(List<String> regions);
}

4嘶是、Observable查詢
當(dāng)執(zhí)行查詢時,您經(jīng)常希望應(yīng)用程序的UI在數(shù)據(jù)更改時自動更新蛛碌。要實現(xiàn)這一點聂喇,請在查詢方法描述中使用類型LiveData的返回值。當(dāng)數(shù)據(jù)庫被更新時,Room生成所有必要的代碼來更新LiveData希太。

@Dao
public interface MyDao {
    @Query("SELECT first_name, last_name FROM user WHERE region IN (:regions)")
    public LiveData<List<User>> loadUsersFromRegionsSync(List<String> regions);
}

5克饶、RXJava的響應(yīng)式查詢
Room還可以從您定義的查詢中返回RXJava2 PublisherFlowable對象。若要使用此功能誊辉,請將androidx.room:room-rxjava2庫添加到gradle的依賴關(guān)系中矾湃。

@Dao
public interface MyDao {
    @Query("SELECT * from user where id = :id LIMIT 1")
    public Flowable<User> loadUserById(int id);

    // Emits the number of users added to the database.
    @Insert
    public Maybe<Integer> insertLargeNumberOfUsers(List<User> users);

    // Makes sure that the operation finishes successfully.
    @Insert
    public Completable insertLargeNumberOfUsers(User... users);

    /* Emits the number of users removed from the database. Always emits at
       least one user. */
    @Delete
    public Single<Integer> deleteUsers(List<User> users);
}

6、直接Cursor訪問

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

四堕澄、 數(shù)據(jù)庫升級

在應(yīng)用程序中添加和更改特性時邀跃,你需要修改實體類以反映這些更改。當(dāng)用戶更新應(yīng)用程序到最新版本時奈偏,不希望它們丟失所有現(xiàn)有數(shù)據(jù)坞嘀,尤其是如果無法從遠(yuǎn)程服務(wù)器恢復(fù)數(shù)據(jù)。

如果您不提供必要的遷移惊来,則Room會重新構(gòu)建數(shù)據(jù)庫丽涩,這意味著您將丟失數(shù)據(jù)庫中的所有數(shù)據(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");
    }
};

五、類型轉(zhuǎn)換器

TypeConverter裁蚁,它將自定義類轉(zhuǎn)換為Room可以保留的已知類型矢渊。例如,如果想要持久化實例Date枉证,可以編寫以下內(nèi)容 TypeConverter 來在數(shù)據(jù)庫中存儲等效的Unix時間戳

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

}

上面的例子中定義了兩個函數(shù)矮男,一個轉(zhuǎn)換Date對象到Long對象,另一個執(zhí)行逆變換室谚,從Long到Date毡鉴。由于Room是知道如何持久化Long對象的,因此它可以使用此轉(zhuǎn)換器來持久保存Date類型的值秒赤。接下來猪瞬,添加 @TypeConverters 注釋到AppDatabase類,這樣Room就可以在AppDatabase中的實體和Dao上使用上面的定義的類型轉(zhuǎn)換器入篮。還可以限制 @TypeConverters 到不同的范圍陈瘦,包括單個實體,DAO和DAO方法潮售。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末痊项,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子酥诽,更是在濱河造成了極大的恐慌鞍泉,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,366評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件肮帐,死亡現(xiàn)場離奇詭異咖驮,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,521評論 3 395
  • 文/潘曉璐 我一進(jìn)店門游沿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人肮砾,你說我怎么就攤上這事诀黍。” “怎么了仗处?”我有些...
    開封第一講書人閱讀 165,689評論 0 356
  • 文/不壞的土叔 我叫張陵眯勾,是天一觀的道長。 經(jīng)常有香客問我婆誓,道長吃环,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,925評論 1 295
  • 正文 為了忘掉前任洋幻,我火速辦了婚禮郁轻,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘文留。我一直安慰自己好唯,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,942評論 6 392
  • 文/花漫 我一把揭開白布燥翅。 她就那樣靜靜地躺著骑篙,像睡著了一般。 火紅的嫁衣襯著肌膚如雪森书。 梳的紋絲不亂的頭發(fā)上靶端,一...
    開封第一講書人閱讀 51,727評論 1 305
  • 那天,我揣著相機與錄音凛膏,去河邊找鬼杨名。 笑死,一個胖子當(dāng)著我的面吹牛译柏,可吹牛的內(nèi)容都是我干的镣煮。 我是一名探鬼主播,決...
    沈念sama閱讀 40,447評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼鄙麦,長吁一口氣:“原來是場噩夢啊……” “哼典唇!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起胯府,我...
    開封第一講書人閱讀 39,349評論 0 276
  • 序言:老撾萬榮一對情侶失蹤介衔,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后骂因,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體炎咖,經(jīng)...
    沈念sama閱讀 45,820評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,990評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了乘盼。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片升熊。...
    茶點故事閱讀 40,127評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖绸栅,靈堂內(nèi)的尸體忽然破棺而出级野,到底是詐尸還是另有隱情,我是刑警寧澤粹胯,帶...
    沈念sama閱讀 35,812評論 5 346
  • 正文 年R本政府宣布蓖柔,位于F島的核電站,受9級特大地震影響风纠,放射性物質(zhì)發(fā)生泄漏况鸣。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,471評論 3 331
  • 文/蒙蒙 一竹观、第九天 我趴在偏房一處隱蔽的房頂上張望镐捧。 院中可真熱鬧,春花似錦栈幸、人聲如沸愤估。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,017評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽玩焰。三九已至,卻和暖如春芍锚,著一層夾襖步出監(jiān)牢的瞬間昔园,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,142評論 1 272
  • 我被黑心中介騙來泰國打工并炮, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留默刚,地道東北人。 一個月前我還...
    沈念sama閱讀 48,388評論 3 373
  • 正文 我出身青樓逃魄,卻偏偏與公主長得像荤西,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子伍俘,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,066評論 2 355

推薦閱讀更多精彩內(nèi)容