Room 是 Google 官方对 SqliteDatabase 的封装库,本文列举了 Room 数据库组件的基本操作。
Room 官方文档:https://developer.android.google.cn/training/data-storage/room

可与 Sqlite 的操作对比来看:https://blog.csdn.net/Gdeer/article/details/88869051

文章目录

  • 一、数据库
    • 1.1 定义
    • 1.2 获取
      • 1.2.1 初始化
      • 1.2.2 升降级
  • 二、表
    • 2.1 定义
    • 2.2 操作
  • 三、数据
    • 3.1 定义
    • 3.2 操作
  • 四、运行
    • 4.1 普通运行
    • 4.2 RxJava 运行
  • 五、SQLite 替换为 Room
  • 六、db 文件覆盖升级
  • 七、其他

一、数据库

1.1 定义

定义一个数据库需要继承 RoomDatabase,并在注解中提供 entities 和 version。它相当于定义了一个 SQLiteOpenHelper,并在其 onCreate 回调中执行创表语句。

entities 即数据库中数据的实体类,用来生成表。
version 即数据库的版本号。

@Database(entities = {City.class}, version = 1)public abstract class ChinaDatabase extends RoomDatabase {    public abstract CityDao cityDao();}

在编译过后,Room 会自动生成 ChinaDatabase 的实现类 ChinaDatabase_Impl。

1.2 获取

通过 Room.databaseBuilder() 来生成一个 RoomDatabase,同时对它进行一些配置,如初始化处理、升降级处理等。

build 不会阻塞主线程,可以在主线程执行。

public static void accessDb(Context context) {    chinaDb = Room.databaseBuilder(context, ChinaDatabase.class, "china-room")        .build();}

1.2.1 初始化

在 build 时,可以添加 Callback,相当于 SQLiteOpenHelper 中的 onCreate 回调。可以在这里进行数据库的初始化操作,如添加一些数据等(SQLiteDatabase 中 onCreate 经常用来建表,但在 Room 中,已经在定义 RoomDatabase 时通过注解生成了)。

这里不能操作 chinaDb(还没初始化),但可以操作 SQLiteDatabase 的封装 db,通过 db 来 execSQL()。

public static void accessDb(Context context) {    chinaDb = Room.databaseBuilder(context, ChinaDatabase.class, "china-room")        .addCallback(new RoomDatabase.Callback() {            @Override            public void onCreate(@NonNull SupportSQLiteDatabase db) {                db.execSQL("insert into city (id, name) values (1, '上海')");            }        })        .build();}

1.2.2 升降级

Room 数据库的升降级通过 Migration 来实现,在 build 时,给 RoomDatabase 加入一个 Migration。

Migration 指明了负责的版本变化路径,即从 startVersion 到 endVersion。当版本号的变化符合该 Migration,那 migrate 就会执行。如版本号 1->2,那 Migration(1, 2) 会执行;版本号 2->1,那 Migration(2, 1) 会执行。

如果当前的版本变化路径没有设置相应的 Migration,那就会抛出一个异常。可以通过 build 时 设置 fallbackToDestructiveMigration 来避免,但这会清除当前数据库的所有数据来重新创建。
如果修改了数据库的模式(修改了 RoomDatabase 的定义),但没有修改版本号,也会抛出一个异常。

public static void accessDb(Context context) {    chinaDb = Room.databaseBuilder(context, ChinaDatabase.class, "china-room.db")        .addMigrations(new Migration(1, 2) {            @Override            public void migrate(@NonNull SupportSQLiteDatabase database) {                database.execSQL("insert into city (city_id, city_name) values ('4', '杭州')");            }        })        .build();}

onCreate 回调、migrate 回调在通过 Dao 执行数据操作时才会执行,所以默认必须在子线程执行。

RoomDatabase 是对 SQLiteOpenHelper 的封装。在 build 时,相当于 new 了一个 SQLiteOpenHelper,但没有调 SQLiteOpenHelper 的 getWritableDatabase 方法,即没有生成一个 SQLiteDatabase 对象。所以 SQLiteOpenHelper 的 onCreate、onUpgrade 方法不会执行,RoomDatabase 的 onCreate、migrate 回调也就没有执行。

二、表

2.1 定义

表的定义与数据的定义合到了一起。定义一个数据,就相当于定义了一个表。

@Entity(tableName = "city")public class City {    @PrimaryKey(autoGenerate = true)    public int id;    @ColumnInfo(name = "city_id")    public String cityId;        @ColumnInfo(name = "city_name")    public String cityName;}

2.2 操作

表的创建、修改、删除都只能在定义阶段进行,在编码时修改数据实体类、数据库类和它们的注解来实现。

@Database(entities = {City.class}, version = 1)public abstract class ChinaDatabase extends RoomDatabase {    public abstract CityDao cityDao();}

也可以通过 usaDatabase.getOpenHelper().getWritableDatabase() 获得 SupportSQLiteDatabase 对象,或在 RoomDatabase build 时通过 onCreate、migrate 回调中的 SupportSQLiteDatabase 对象进行 db.execSQL("") 操作来修改表,但这会打乱所有的东西,产生异常。

三、数据

3.1 定义

如上节所述,与表的定义合在一起。

3.2 操作

Room 对数据的操作通过 Dao (data access objects)来实现。

Dao 可以是接口,也可以是抽象类。

@Daopublic interface CityDao {    @Insert    void insertCity(City city);    @Delete    void deleteCity(City city);    @Delete    void deleteAllCity(City...city);        @Update    void updateCity(City city);    @Query("select * from city")    List<City> getAllCity();    @Query("select * from city where city_id = (:cityId)")    City getCity(String cityId);}

@Insert、@Delete、@Update 方法都可以选择返回一个数值,用来标识执行成功的条数。

@Query 中的 sql 语句会在编译时检测,如果不正确会编译失败。

在编译过后,Room 会自动生成 CityDao 的实现类 CityDao_Impl。

注意:sql 表名称是纯小写的,如果类是 UsaCity,sql 语句中应该是 usa_city。

四、运行

4.1 普通运行

Room 默认不支持在主线程进行数据访问,因为有可能阻塞线程。通过在 build 时调用 allowMainThreadQueries() 可去除这个限制。

Dao 的操作最终都是通过 getWritableDatabase.xxx() 来实现的。

//DbActivity.javapublic void onCreate(Bundle savedInstanceState) {...    new Thread(new Runnable() {        @Override        public void run() {SqliteBehavior.behave(DbActivity.this);        }    }).start();}//SqliteBehavior.javapublic static void behave(Context context) {    accessDb(context);    CityDao cityDao = chinaDb.cityDao();    cityDao.deleteAllCity(cityDao.getAllCity().toArray(new City[0]));    Log.d(TAG, "after deleteAll: " + cityDao.getAllCity());            cityDao.insertCity(new City("1", "北京"));    cityDao.insertCity(new City("2", "上海"));    cityDao.insertCity(new City("3", "广州"));    Log.d(TAG, "after insert: " + cityDao.getAllCity());    City city = cityDao.getCity("3");    city.setCityName("深圳");    cityDao.updateCity(city);    Log.d(TAG, "after update: " + cityDao.getAllCity());    cityDao.deleteCity(city);    Log.d(TAG, "after delete: " + cityDao.getAllCity());}

输出

D/database-room: after deleteAll: []D/database-room: after insert: [City{id=4, cityId='1', cityName='北京'}, City{id=5, cityId='2', cityName='上海'}, City{id=6, cityId='3', cityName='广州'}]D/database-room: after update: [City{id=4, cityId='1', cityName='北京'}, City{id=5, cityId='2', cityName='上海'}, City{id=6, cityId='3', cityName='深圳'}]D/database-room: after delete: [City{id=4, cityId='1', cityName='北京'}, City{id=5, cityId='2', cityName='上海'}]

4.2 RxJava 运行

todo

五、SQLite 替换为 Room

将原有的 SQLite 替换为 Room,只需创建 Room 所需类: RoomDatabase、CityDao、City,替换即可。

注意点:

  • 给 RoomDatabase 的版本号 +1
    否则会报错:
java.lang.IllegalStateException: Room cannot verify the data integrity. Looks like you've changed schema but forgot to update the version number. You can simply fix this by increasing the version number.
  • 添加 Migration(old, old + 1)
    否则会报错,缺少 Migration。

  • 建表语句要完全一致,即字段的名称、约束都要完全一致
    Room 中数据的实体类中,int 这样的基本类型,在创表时,会添加 not null 约束。
    而如果之前的 SQLite 数据库的建表语句中没有 not null 约束,那迁移时就会报错:

java.lang.IllegalStateException: Migration didn't properly handle city(com.gdeer.gdtesthub.db.room.City). Expected:TableInfo{name='city', columns={city_name=Column{name='city_name', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0}, id=Column{name='id', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=1}, city_id=Column{name='city_id', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0}}, foreignKeys=[], indices=[]} Found:TableInfo{name='city', columns={city_name=Column{name='city_name', type='text', affinity='2', notNull=false, primaryKeyPosition=0}, id=Column{name='id', type='integer', affinity='3', notNull=false, primaryKeyPosition=1}, city_id=Column{name='city_id', type='text', affinity='2', notNull=false, primaryKeyPosition=0}}, foreignKeys=[], indices=[]}

可以看到 Expected 的 id 的 notNull 是 ture,Found 的 id 的 notNull 是 false。

要解决这个问题,将 Room 的 City 实体类的 id 改为 Integer 类型,这样建表时就不会添加 not null 约束了。

如果有的列需要 not null 约束,那给那个字段加上 @NonNULL 的注解就好。

六、db 文件覆盖升级

通过 db 文件覆盖升级时,不用修改 RoomDatabase 的版本号,在 build 之前替换掉文件即可。

修改版本号,添加 Migration,在 migrate 中覆盖反而有问题,会抛异常 SQLiteException: attempt to write a readonly database

七、其他

  • setJournalMode()

RoomDatabase 在 build 时可以通过 setJournalMode() 来设置读写模式。读写模式分为 TRUNCATEWRITE_AHEAD_LOGGINGAUTOMATIC。默认是 AUTOMATIC,它会判断手机版本号,16及以上会使用 WRITE_AHEAD_LOGGING,以下会使用 TRUNCATE。

当使用 TRUNCATE,数据库读写都不能并发。数据会实时写入 db。

当使用 WRITE_AHEAD_LOGGING,数据库读可并发,写不能并发。数据不会实时写入 db(可能在 wal 文件内)。

更多相关文章

  1. Android发送xml数据给服务器的方法
  2. Android对话框自定义标题 对话框标题美化操作
  3. Android(安卓)实现Json数据解析,并进行应用!
  4. Android开发中颜色的自定义方法
  5. Android(安卓)使用jsoup 进行数据抓取
  6. Android网络数据开关用法简单示例
  7. [Android]自定义简易版日历控件
  8. 初学Android,使用Drawable资源之使用ShapeDrawable资源(十五)
  9. Android(安卓)自定义Dialog、IOS风格;

随机推荐

  1. android 下载编译环境配置简要
  2. 24-Android监听精确的通话状态
  3. android篇-如何做一个简单的安卓源码查看
  4. 快捷方式
  5. Android(安卓)EventBus(普通事件/粘性事件
  6. Android reboot分析
  7. Android(安卓)Studio从入门到精通的免费
  8. Android(安卓)style
  9. Android(安卓)修改Menu背景
  10. 【移动开发】Android中异步加载数据(二)Asy