Room持久性庫在SQLite上提供了一個抽象層溃列,幫助開發(fā)者更友好、流暢的訪問SQLite數(shù)據(jù)庫。
以下是Room的初步使用經(jīng)驗笋除,可供基礎(chǔ)功能的使用。完整的文檔請參考https://developer.android.com/training/data-storage/room
一炸裆、添加依賴
// room
implementation "androidx.room:room-runtime:2.2.6"
annotationProcessor "androidx.room:room-compiler:2.2.6"
// optional - RxJava support for Room
implementation "androidx.room:room-rxjava2:2.2.6"
//rxAndroid
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
二垃它、創(chuàng)建Entity
- 一個Entity類對應一個表結(jié)構(gòu)。
- 數(shù)據(jù)模型中的每個字段都會映射為數(shù)據(jù)庫表中的一列烹看。
- 必須為類添加@Entity注解国拇,為主鍵添加@PrimaryKey注解。
-
@Entity - 類注解
tableName
:用于指定表名惯殊,未指定時使用類名作為表名酱吝。 -
@PrimaryKey - 字段注解:表明該字段是主鍵,一個類中一定要有一個主鍵土思。
autoGenerate
:設(shè)置主鍵自增务热,自增主鍵必須為int型忆嗜。 -
@ColumnInfo - 字段注解
name
:設(shè)置字段在表中的列名,未指定時使用字段名稱崎岂。 - @Ignore - 字段注解:不需要存儲在數(shù)據(jù)庫中的字段可以使用該注解捆毫。
- 數(shù)據(jù)模型需要添加set、get方法供room使用该镣。
三冻璃、創(chuàng)建Dao
- Dao定義為一個接口。
- 必須為類添加@Dao注解损合,在每個需要訪問數(shù)據(jù)庫中的方法上添加@Insert省艳、@Delete、@Update嫁审、@Query注解跋炕。
- 一般情況下,對于同一個表的數(shù)據(jù)庫操作會放到一個Dao類中律适。
- @Dao - 類注解
-
@Insert - 方法注解
將傳入的數(shù)據(jù)插入到數(shù)據(jù)庫表中辐烂。插入數(shù)據(jù)的id會放在列表中返回。@Insert Single<List<Long>> insert(Product... products);
-
@Delete - 方法注解
刪除數(shù)據(jù)庫表中的數(shù)據(jù)捂贿,會按照主鍵查找刪除纠修,會返回成功刪除的數(shù)目。@Delete Single<Integer> delete(Product... products);
-
@Update - 方法注解
根據(jù)主鍵更新表中數(shù)據(jù)厂僧,會返回成功更新的數(shù)目扣草。@Update Single<Integer> update(Product... products);
-
@Query - 方法注解
查詢數(shù)據(jù)庫的注解,需要自己書寫sql語句颜屠。
此處推薦使用Flowable辰妙,在數(shù)據(jù)更新時會自動觸發(fā)查詢,但要在頁面關(guān)閉的時候及時注銷甫窟,F(xiàn)lowable默認在子線程查詢密浑,不需要手動切換線程。
需要傳入?yún)?shù)進行查詢的時候可以使用“:id”占位符粗井。@Query("select * from product") Flowable<List<Product>> getAll();
@Query("select * from product where id=:id") Flowable<Product> findById(int id); @Query("select * from product where name like :name") Flowable<List<Product>> findByName(String name);
四尔破、創(chuàng)建DataBase
- 此類為抽象類,需要繼承自androidx.room.RoomDatabase
- 必須為類添加@Database注解浇衬。
- 對于需要暴露的Dao懒构,請?zhí)砑荧@取對應Dao的抽象方法。
public abstract ProductDao productDao();
- 建議使用工具類或在本類實現(xiàn)單例模式供整個App使用径玖,大量創(chuàng)建DataBase會消耗一定的資源。
-
@Database - 類注解
version
:int型的版本號颤介,用于標識數(shù)據(jù)庫的版本梳星。在數(shù)據(jù)庫升級的時候需要增加version的數(shù)字赞赖。
entities
:該字段為Class<?>[]類型,需要將數(shù)據(jù)庫中所包含表的Entity類添加到注解中冤灾。entities的格式為“entities = {Product.class}”前域。
exportSchema
:控制編譯器是否輸出數(shù)據(jù)庫結(jié)構(gòu)文件,默認為true韵吨,需要配置room.schemaLocation
字段才能看到輸出結(jié)果匿垄。
配置方法:
在App級別的build.gradle
文件中添加數(shù)據(jù)庫結(jié)構(gòu)導出配置,添加配置信息后重新編譯工程归粉,工程中的database目錄下會生成當前版本數(shù)據(jù)庫的表結(jié)構(gòu)椿疗。(文件名為1.json
)。android { // ... defaultConfig { // ... javaCompileOptions { annotationProcessorOptions { arguments += ["room.schemaLocation": "$projectDir/database".toString()] } } } }
五糠悼、 使用
- 構(gòu)建Database單例
private static final String DB_NAME = "localData.db"; private static volatile AppDatabase INSTANCE; public static AppDatabase getInstance(Context context) { if (INSTANCE == null) { synchronized (AppDatabase.class) { if (INSTANCE == null) { INSTANCE = Room.databaseBuilder(context.getApplicationContext(), AppDatabase.class, DB_NAME).build(); } } } return INSTANCE; }
- 進行數(shù)據(jù)插入
由于Entity中的主鍵設(shè)置了自增届榄,所以在插入數(shù)據(jù)的時候不需要設(shè)置id。Product product1 = new Product(); product1.setJdID("jdId_1 by insert"); product1.setName("name_1 by insert"); Product product2 = new Product(); product2.setJdID("jdId_2 by insert"); product2.setName("name_2 by insert"); Disposable disposable = AppDatabase.getInstance(this).productDao().insert(product1, product2) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Consumer<List<Long>>() { @Override public void accept(List<Long> ids) throws Exception { System.out.println("insert number = " + ids.size()); for (long id : ids) { System.out.println("insert id = " + id); } } }, new Consumer<Throwable>() { @Override public void accept(Throwable throwable) throws Exception { System.out.println("insert error = " + throwable.getMessage()); throwable.printStackTrace(); } });
- 刪除數(shù)據(jù)
刪除數(shù)據(jù)的接口使用的是@Delete注解倔喂,自動生成的方法僅支持根據(jù)傳入對象的主鍵進行刪除铝条,如果需要使用其他刪除條件,建議使用@Query注解并手動編寫sql語句席噩。Product product = new Product(); product.setId(6); Disposable disposable = AppDatabase.getInstance(this).productDao().delete(product) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Consumer<Integer>() { @Override public void accept(Integer integer) throws Exception { System.out.println("delete number = " + integer); } }, new Consumer<Throwable>() { @Override public void accept(Throwable throwable) throws Exception { System.out.println("delete error = " + throwable.getMessage()); throwable.printStackTrace(); } });
- 修改數(shù)據(jù)
修改數(shù)據(jù)的接口使用的是@Update注解班缰,自動生成的方法僅支持根據(jù)傳入對象的主鍵進行更新,如果需要使用其他更新條件悼枢,建議使用@Query注解并手動編寫sql語句埠忘。Product product = new Product(); product.setId(2); product.setName("name by update"); Disposable disposable = AppDatabase.getInstance(this).productDao().update(product) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Consumer<Integer>() { @Override public void accept(Integer integer) throws Exception { System.out.println("update number = " + integer); } }, new Consumer<Throwable>() { @Override public void accept(Throwable throwable) throws Exception { System.out.println("update error = " + throwable.getMessage()); throwable.printStackTrace(); } });
- 查詢數(shù)據(jù)
查詢數(shù)據(jù)方法返回的是一個Flowable,在對應表更新數(shù)據(jù)之后會重新進行查詢并返回結(jié)果萧芙。
由于數(shù)據(jù)更新的單位是整張表给梅,更新的數(shù)據(jù)不在查詢范圍內(nèi)時依舊會重新查詢并返回結(jié)果,可以使用distinctUntilChanged操作符將連續(xù)重復的數(shù)據(jù)過濾掉双揪。Disposable disposable = AppDatabase.getInstance(this).productDao().getAll() .distinctUntilChanged() .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Consumer<List<Product>>() { @Override public void accept(List<Product> products) throws Exception { System.out.println("query number = " + products.size()); for (Product product : products) { System.out.println("query = " + product); } } }, new Consumer<Throwable>() { @Override public void accept(Throwable throwable) throws Exception { System.out.println("query error = " + throwable.getMessage()); throwable.printStackTrace(); } });
- 檢查
以上方法返回的Disposable都未進行處理动羽,數(shù)據(jù)庫操作是在異步線程執(zhí)行的,為防止回調(diào)時出現(xiàn)錯誤渔期,請將返回的disposable與頁面生命周期進行綁定运吓,及時終止操作。
六疯趟、數(shù)據(jù)庫升級
數(shù)據(jù)庫的表結(jié)構(gòu)很難一次性定義完整拘哨,有時會根據(jù)業(yè)務的變化而修改數(shù)據(jù)庫的表結(jié)構(gòu),修改表結(jié)構(gòu)就要對數(shù)據(jù)庫進行升級信峻,升級的流程如下:
- 創(chuàng)建或修改Entity
現(xiàn)在就可以根據(jù)業(yè)務需要調(diào)整數(shù)據(jù)庫中的表結(jié)構(gòu)了倦青,由于表結(jié)構(gòu)是映射為Entity類,所以按照需求調(diào)整Entity類就好了盹舞。 - 修改
@Database
注解中的參數(shù)- 升級版本號
- 更新
entities
中的參數(shù)产镐,使其與新版本的表結(jié)構(gòu)一致隘庄。
- 創(chuàng)建Migration類
- 創(chuàng)建一個繼承自
Migration
的類或直接實現(xiàn)一個Migration
匿名類。 - 在構(gòu)造函數(shù)中指定數(shù)據(jù)庫升級時舊的版本號和新的版本號癣亚。
- 實現(xiàn)
migrate
方法丑掺,migrate
方法中會附帶一個database
參數(shù),直接通過database.execSQL("")
方法執(zhí)行數(shù)據(jù)庫表的更新語句述雾。以下列舉幾個常見的數(shù)據(jù)庫更新Migration
街州。
添加新表
添加字段private static class Migration1_2 extends Migration { public Migration1_2() { super(1, 2); } @Override public void migrate(@NonNull SupportSQLiteDatabase database) { database.execSQL("CREATE TABLE IF NOT EXISTS 'log' ('id' INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, 'time' INTEGER NOT NULL, 'code' INTEGER NOT NULL, 'message' TEXT)"); } }
修改表名:我之前創(chuàng)建的表名是Product,希望修改成product玻孟。private static class Migration2_3 extends Migration { public Migration2_3() { super(2, 3); } @Override public void migrate(@NonNull SupportSQLiteDatabase database) { database.execSQL("alter table 'product' add 'enable' integer not null default '1'"); } }
private static class Migration3_4 extends Migration { public Migration3_4() { super(3, 4); } @Override public void migrate(@NonNull SupportSQLiteDatabase database) { // 數(shù)據(jù)庫中的表名在使用時不區(qū)分大小寫唆缴,可在展示的時候是有大寫字母的,所以在修改表名的時候先轉(zhuǎn)換成一個臨時的表名取募。 database.execSQL("alter table 'Product' rename to 'product_tmp'"); database.execSQL("alter table 'product_tmp' rename to 'product'"); } }
- 創(chuàng)建一個繼承自
- 將自定義的
Migration
對象通過addMigrations
方法添加到數(shù)據(jù)庫初始化方法中琐谤,此方法可以添加不定數(shù)量的Migration
并根據(jù)版本號依次執(zhí)行。Room.databaseBuilder(application.getApplicationContext(), AppDatabase.class, DB_NAME) .addMigrations( new Migration1_2(), new Migration2_3(), new Migration3_4()) .build();
- 重新編譯安裝app玩敏,在使用數(shù)據(jù)庫的時候會自動根據(jù)上面的配置信息進行數(shù)據(jù)庫升級斗忌,由于數(shù)據(jù)庫升級會消耗一定的時間,第一次調(diào)用數(shù)據(jù)庫的方法時間會稍長一點旺聚。
七织阳、進階功能
對于小型的app,基本的數(shù)據(jù)庫功能就足以滿足業(yè)務需求砰粹,下面這些進階的功能暫未嘗試唧躲。
-
@ForeignKey注解
數(shù)據(jù)庫中的外鍵,用于確定表與表的關(guān)系碱璃。 -
@TypeConverters注解
數(shù)據(jù)庫的類型轉(zhuǎn)換器弄痹,復雜的數(shù)據(jù)結(jié)構(gòu)可以通過類型轉(zhuǎn)換器轉(zhuǎn)換成簡單的數(shù)據(jù)再存放到數(shù)據(jù)庫中。 -
@Index注解
數(shù)據(jù)庫的索引嵌器,常用于大型的數(shù)據(jù)庫中肛真,可以減少超大數(shù)據(jù)量的查詢時間。