Android官方Room配合RxJava接入及使用经验

Room持久性库在SQLite上提供了一个抽象层,帮助开发者更友好、流畅的访问SQLite数据库。
以下是Room的初步使用经验,可供基础功能的使用。完整的文档请参考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'

二、创建Entity

  • 一个Entity类对应一个表结构。
  • 数据模型中的每个字段都会映射为数据库表中的一列。
  • 必须为类添加@Entity注解,为主键添加@PrimaryKey注解。
  • @Entity - 类注解
    tableName:用于指定表名,未指定时使用类名作为表名。
  • @PrimaryKey - 字段注解:表明该字段是主键,一个类中一定要有一个主键。
    autoGenerate:设置主键自增,自增主键必须为int型。
  • @ColumnInfo - 字段注解
    name:设置字段在表中的列名,未指定时使用字段名称。
  • @Ignore - 字段注解:不需要存储在数据库中的字段可以使用该注解。
  • 数据模型需要添加set、get方法供room使用。

三、创建Dao

  • Dao定义为一个接口。
  • 必须为类添加@Dao注解,在每个需要访问数据库中的方法上添加@Insert、@Delete、@Update、@Query注解。
  • 一般情况下,对于同一个表的数据库操作会放到一个Dao类中。
  • @Dao - 类注解
  • @Insert - 方法注解
    将传入的数据插入到数据库表中。插入数据的id会放在列表中返回。
    @Insert
    Single<List<Long>> insert(Product... products);
    
  • @Delete - 方法注解
    删除数据库表中的数据,会按照主键查找删除,会返回成功删除的数目。
    @Delete
    Single<Integer> delete(Product... products);
    
  • @Update - 方法注解
    根据主键更新表中数据,会返回成功更新的数目。
    @Update
    Single<Integer> update(Product... products);
    
  • @Query - 方法注解
    查询数据库的注解,需要自己书写sql语句。
    此处推荐使用Flowable,在数据更新时会自动触发查询,但要在页面关闭的时候及时注销,Flowable默认在子线程查询,不需要手动切换线程。
    @Query("select * from product")
    Flowable<List<Product>> getAll();
    
    需要传入参数进行查询的时候可以使用“:id”占位符。
    @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);
    

四、创建DataBase

  • 此类为抽象类,需要继承自androidx.room.RoomDatabase
  • 必须为类添加@Database注解。
  • 对于需要暴露的Dao,请添加获取对应Dao的抽象方法。
    public abstract ProductDao productDao();
    
  • 建议使用工具类或在本类实现单例模式供整个App使用,大量创建DataBase会消耗一定的资源。
  • @Database - 类注解
    version:int型的版本号,用于标识数据库的版本。在数据库升级的时候需要增加version的数字。
    entities:该字段为Class<?>[]类型,需要将数据库中所包含表的Entity类添加到注解中。entities的格式为“entities = {Product.class}”。
    exportSchema:控制编译器是否输出数据库结构文件,默认为true,需要配置room.schemaLocation字段才能看到输出结果。
    配置方法:
    在App级别的build.gradle文件中添加数据库结构导出配置,添加配置信息后重新编译工程,工程中的database目录下会生成当前版本数据库的表结构。(文件名为1.json)。
    android {
        // ...
        defaultConfig {
            // ...
            javaCompileOptions {
                annotationProcessorOptions {
                    arguments += ["room.schemaLocation": "$projectDir/database".toString()]
                }
            }
        }
    }
    

五、 使用

  1. 构建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;
    }
    
  2. 进行数据插入
    由于Entity中的主键设置了自增,所以在插入数据的时候不需要设置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();
                }
            });
    
  3. 删除数据
    删除数据的接口使用的是@Delete注解,自动生成的方法仅支持根据传入对象的主键进行删除,如果需要使用其他删除条件,建议使用@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();
                }
            });
    
  4. 修改数据
    修改数据的接口使用的是@Update注解,自动生成的方法仅支持根据传入对象的主键进行更新,如果需要使用其他更新条件,建议使用@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();
                }
            });
    
  5. 查询数据
    查询数据方法返回的是一个Flowable,在对应表更新数据之后会重新进行查询并返回结果。
    由于数据更新的单位是整张表,更新的数据不在查询范围内时依旧会重新查询并返回结果,可以使用distinctUntilChanged操作符将连续重复的数据过滤掉。
    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();
                }
            });
    
  6. 检查
    以上方法返回的Disposable都未进行处理,数据库操作是在异步线程执行的,为防止回调时出现错误,请将返回的disposable与页面生命周期进行绑定,及时终止操作。

六、数据库升级

数据库的表结构很难一次性定义完整,有时会根据业务的变化而修改数据库的表结构,修改表结构就要对数据库进行升级,升级的流程如下:

  1. 创建或修改Entity
    现在就可以根据业务需要调整数据库中的表结构了,由于表结构是映射为Entity类,所以按照需求调整Entity类就好了。
  2. 修改@Database注解中的参数
    • 升级版本号
    • 更新entities中的参数,使其与新版本的表结构一致。
  3. 创建Migration类
    • 创建一个继承自Migration的类或直接实现一个Migration匿名类。
    • 在构造函数中指定数据库升级时旧的版本号和新的版本号。
    • 实现migrate方法,migrate方法中会附带一个database参数,直接通过database.execSQL("")方法执行数据库表的更新语句。以下列举几个常见的数据库更新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)");
          }
      }
      
      添加字段
      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'");
          }
      }
      
      修改表名:我之前创建的表名是Product,希望修改成product。
      private static class Migration3_4 extends Migration {
      
          public Migration3_4() {
              super(3, 4);
          }
      
          @Override
          public void migrate(@NonNull SupportSQLiteDatabase database) {
              // 数据库中的表名在使用时不区分大小写,可在展示的时候是有大写字母的,所以在修改表名的时候先转换成一个临时的表名。
              database.execSQL("alter table 'Product' rename to 'product_tmp'");
              database.execSQL("alter table 'product_tmp' rename to 'product'");
          }
      }
      
  4. 将自定义的Migration对象通过addMigrations方法添加到数据库初始化方法中,此方法可以添加不定数量的Migration并根据版本号依次执行。
    Room.databaseBuilder(application.getApplicationContext(), AppDatabase.class, DB_NAME)
            .addMigrations(
                new Migration1_2(),
                new Migration2_3(),
                new Migration3_4())
            .build();
    
  5. 重新编译安装app,在使用数据库的时候会自动根据上面的配置信息进行数据库升级,由于数据库升级会消耗一定的时间,第一次调用数据库的方法时间会稍长一点。

七、进阶功能

对于小型的app,基本的数据库功能就足以满足业务需求,下面这些进阶的功能暂未尝试。

  • @ForeignKey注解
    数据库中的外键,用于确定表与表的关系。
  • @TypeConverters注解
    数据库的类型转换器,复杂的数据结构可以通过类型转换器转换成简单的数据再存放到数据库中。
  • @Index注解
    数据库的索引,常用于大型的数据库中,可以减少超大数据量的查询时间。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343

推荐阅读更多精彩内容