前面演示兩位room數據庫的基本使用,今天來看一下數據庫的升級/遷移。本文將以新增表和新增列爲例來講解。
這裏用到一個數據庫調試工具Stetho,大家可以去看看用法:https://github.com/facebook/stetho
1.新加一個數據表
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.
如果找不到舊版本數據庫遷移到新版本數據庫的遷移文件,並且設置了此方法,那麼數據庫將會刪除重建
* <p>
* When the database version on the device does not match the latest schema version, Room
* runs necessary {@link Migration}s on the database.
* <p>
* 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}.
* <p>
* You can call this method to change this behavior to re-create the database instead of
* crashing.
* <p>
一般情況下,當設備數據庫版本號與最新架構的版本號不一致時,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將嘗試使用相同的文件創建數據庫。否則將刪除所有的數據表
* <p>
* 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<T> fallbackToDestructiveMigration() {
mRequireMigration = false;
mAllowDestructiveMigrationOnDowngrade = true;
return this;
}
2.利用fallbackToDestructiveMigration()強制升級:
看一下使用了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.使用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.增加數據表的列的數據庫升級
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,以上內容就是數據庫升級/遷移