前面演示两位room数据库的基本使用,今天来看一下数据库的升级/迁移。本文将以新增表和新增列为例来讲解。
这里用到一个数据库调试工具Stetho,大家可以去看看用法:https://github.com/facebook/stetho
1.1.这样定义未指定主键不能为null,会报错如下:
@Entity(tableName = "device",primaryKeys = {"id"})
public class Device {
private String id;
private String location;
private String deviceName;
private String deviceType;
...
}
错误: You must annotate primary keys with @NonNull. "id" is nullable. SQLite considers this a bug and Room does not allow it. See SQLite docs for details: https://www.sqlite.org/lang_createtable.html
所以需要指定主键以及主键不能为null,添加注解@@NonNull
@Entity(tableName = "device",primaryKeys = {"id"})
public class Device {
@NonNull //主键不允许为null值
private String id;
private String location;
private String deviceName;
private String deviceType;
...
}
1.2.如果忘记在database的entities中加入我们刚才新建的类,会报如下错误:
错误: There is a problem with the query: [SQLITE_ERROR] SQL error or missing database (no such table: device)
找不到这个表
1.3.如果忘记更新版本号了会报错:
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.
译:room无法验证数据库完整性,看起来是你改变了架构但是忘记更新数据库版本了。你只需要增加版本号就可以解决这个问题
1.4.你以为你增加了版本号就解决了
当然,编译时通过了,不过运行的时候就会发现出现了如下问题:
java.lang.IllegalStateException: A migration from 1 to 2 was required but not found. Please provide the necessary Migration path via RoomDatabase.Builder.addMigration(Migration ...) or allow for destructive migrations via one of the RoomDatabase.Builder.fallbackToDestructiveMigration* methods.
译:没有找到数据库从版本号1迁移到版本号2的迁移文件。请通过RoomDatabase.Builder.addMigration()方法增加迁移文件,或者RoomDatabase.Builder.fallbackToDestructiveMigration()方法进行处理。
1.5.OK,既然有处理方案,那么先捡简单的来
在创建数database实例的时候,就是通过RoomDatabase.Builder.build()来完成的,我们就在build之前增加这个fallbackToDestructiveMigration()方法试试。先看看系统对fallbackToDestructiveMigration()方法的介绍
/**
* Allows Room to destructively recreate database tables if {@link Migration}s that would
* migrate old database schemas to the latest schema version are not found.
如果找不到旧版本数据库迁移到新版本数据库的迁移文件,并且设置了此方法,那么数据库将会删除重建
*
* When the database version on the device does not match the latest schema version, Room
* runs necessary {@link Migration}s on the database.
*
* If it cannot find the set of {@link Migration}s that will bring the database to the
* current version, it will throw an {@link IllegalStateException}.
*
* You can call this method to change this behavior to re-create the database instead of
* crashing.
*
一般情况下,当设备数据库版本号与最新架构的版本号不一致时,room将在数据库上运行必要的Migration文件,如果找不到对应的Migration将会抛出异常IllegalStateException,你可以调用此方法来重新创建数据库而不是抛出异常。
* If the database was create from an asset or a file then Room will try to use the same
* file to re-create the database, otherwise this will delete all of the data in the
* database tables managed by Room.
如果数据库是使用asset或者file创建的,则room将尝试使用相同的文件创建数据库。否则将删除所有的数据表
*
* To let Room fallback to destructive migration only during a schema downgrade then use
* {@link #fallbackToDestructiveMigrationOnDowngrade()}.
*
* @return This {@link Builder} instance.
*
* @see #fallbackToDestructiveMigrationOnDowngrade()
*/
@NonNull
public Builder fallbackToDestructiveMigration() {
mRequireMigration = false;
mAllowDestructiveMigrationOnDowngrade = true;
return this;
}
看一下使用了fallbackToDestructiveMigration()方法后的效果,代码:
public static AppDataBase getInstance(Context context) {
if (instance == null) {
synchronized (AppDataBase.class) {
if (instance == null) {
instance = Room.databaseBuilder(context.getApplicationContext(), AppDataBase.class, DATABASE_NAME)
.fallbackToDestructiveMigration()//数据库更新时删除数据重新创建
.build();
}
}
}
return instance;
}
效果如下,创建成功:
ok,这是最简单的数据库更新,接下来我们来看一下通过Migration来更新数据库。
3.1.首先,先定义我们版本升级的策略,如我们现在是需要增加一个表device
/**
* 版本1-2的迁移策略
* 构造方法需传 开始版本号 与 截止版本号
*/
static final Migration MIGRATION_1_2 = new Migration(1,2) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
//将数据表device创建出来
database.execSQL("CREATE TABLE 'device' ('id' TEXT NOT NULL,'location' TEXT,'deviceName' TEXT,'deviceType' TEXT,PRIMARY KEY ('id')) ");
}
};
3.2.然后,在数据库初始化的时候加入版本1-2的迁移策略,这次我们不使用fallbackToDestructiveMigration方法
public static AppDataBase getInstance(Context context) {
if (instance == null) {
synchronized (AppDataBase.class) {
if (instance == null) {
instance = Room.databaseBuilder(context.getApplicationContext(), AppDataBase.class, DATABASE_NAME)
// .fallbackToDestructiveMigration()//数据库更新时删除数据重新创建
.addMigrations(MIGRATION_1_2)//指定版本1-2升级时的升级策略
.build();
}
}
}
return instance;
}
3.3.最后看效果:
4.1.接下来,我们试一下给device表增加一列,不需要默认值,
@Entity(tableName = "device",primaryKeys = {"id"})
public class Device {
@NonNull //主键不允许为null值
private String id;
private String location;
private String deviceName;
private String deviceType;
//新增一列
private String deviceCode;
...
}
如果不生成对应的get和set方法,会报错:
错误: Cannot find getter for field.解决这个问题有两种方法
a.不需要在数据表中生成此列,可以直接加上@Ignore注解,(这样操作的不用后续操作了)
b.生成对应的get与set方法(即需要变更数据库,需要继续操作)
注意:记得更新数据库版本号和新增数据库迁移策略,否则将会重现3、4步骤周出现的问题
迁移策略
/**
* 版本2-3的迁移策略
* 构造方法需传 开始版本号2 与 截止版本号3
*/
static final Migration MIGRATION_2_3 = new Migration(2,3) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
//为device表增加一列
database.execSQL("ALTER TABLE device ADD COLUMN deviceCode TEXT ");
}
};
。。。
public static AppDataBase getInstance(Context context) {
if (instance == null) {
synchronized (AppDataBase.class) {
if (instance == null) {
instance = Room.databaseBuilder(context.getApplicationContext(), AppDataBase.class, DATABASE_NAME)
// .fallbackToDestructiveMigration()//数据库更新时删除数据重新创建
.addMigrations(MIGRATION_1_2,MIGRATION_2_3)//指定版本升级时的升级策略
.build();
}
}
}
return instance;
}
然后测试如下图,新的列已经添加成功:
注意:版本升级时需要下次操作数据库时才会生效的!!!
4.2.接下来我们再给device表增加一列,并且加默认值:
同样的,在实体类里面增加一个字段—>添加版本迁移策略—>更新database版本号,运行代码
/**
* 版本3-4的迁移策略
* 构造方法需传 开始版本号3 与 截止版本号4
*/
static final Migration MIGRATION_3_4 = new Migration(3,4) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
//为device表增加一列
database.execSQL("ALTER TABLE device ADD COLUMN managerType TEXT NOT NULL DEFAULT 'A类' ");
}
};
。。。
public static AppDataBase getInstance(Context context) {
if (instance == null) {
synchronized (AppDataBase.class) {
if (instance == null) {
instance = Room.databaseBuilder(context.getApplicationContext(), AppDataBase.class, DATABASE_NAME)
// .fallbackToDestructiveMigration()//数据库更新时删除数据重新创建
.addMigrations(MIGRATION_1_2,MIGRATION_2_3,MIGRATION_3_4)//指定版本升级时的升级策略
.build();
}
}
}
return instance;
}
OK,原有的数据也加上了默认值了
ok,以上内容就是数据库升级/迁移