一直覺得更新資料庫schema是很讓人緊張的事,如果出錯的話App的某些功能就會壞掉,甚至是App一打開就壞了。隨著更新次數增加,還要持續留意從某個舊版本升上來會不會出錯,真是越來越緊張。
慶幸的是,Room在這方面提供了很完善的實作和測試機制,我們今天先看實作的部分,測試就留到明天。
Room透過Migration來得知每次更新的具體內容,我們直接實作一個簡單的更新,例如要在Repo加上html_url欄位的話:
1.修改Entity
加上要新增的欄位html_url,其餘不變。
@Entity(...)
public class Repo {
...
public String html_url;
public Repo(...) {
...
}
}
2.修改版本號
將version從1改成2。
@Database(entities = {RepoSearchResult.class, Repo.class}, version = 2)
public abstract class GithubDb extends RoomDatabase {
abstract public RepoDao repoDao();
}
3.建立Migration
透過constructor Migration(int startVersion, int endVersion)
建立版本1升級到2的內容。
@Database(entities = {RepoSearchResult.class, Repo.class}, version = 2)
public abstract class GithubDb extends RoomDatabase {
...
public static final Migration MIGRATION_1_2 = new Migration(1, 2) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
database.execSQL("ALTER TABLE Repo "
+ "ADD COLUMN html_url TEXT");
}
};
}
4.加至Builder
在databaseBuilder中加入這個Migration。
Room.databaseBuilder(app, GithubDb.class,"github.db")
.addMigrations(MIGRATION_1_2)
.build();
完成,以上四個就是簡單的更新步驟。
將來有其他更新時就重複1~3步驟,並在4加入新的Migration就可以了,例如:
Room.databaseBuilder(app, GithubDb.class,"github.db")
.addMigrations(MIGRATION_1_2, MIGRATION_2_3)
.build();
若有步驟漏掉時,Room會拋出IllegalStateException
,並帶有蠻詳細的錯誤訊息,例如我們修改了Entity而沒實作其他步驟,會收到錯誤訊息:
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.
照著訊息的指引更新版本號,還是會錯,但收到不一樣的訊息:
java.lang.IllegalStateException: A migration from 1 to 2 is necessary. Please provide a Migration in the builder or call fallbackToDestructiveMigration in the builder in which case Room will re-create all of the tables.
錯誤訊息給我們兩個選項,一是建立Migration,這樣就如同將上面的四個步驟做完了,所以其實不用怕漏掉步驟,Room都會提示;而第二個選項則是在builder加上fallbackToDestructiveMigration()
,這樣的話Room會直接把table重建,原本的資料會全部清空,除非還在內部開發中,否則一般都不建議這樣做。
如果原本是使用SQLiteDatabase,要轉移到Room的過程非常簡單,只要讓Room升級到版本2並提供一個空的Migration就可以了,節錄自Google sample
/**
* Migrate from:
* version 1 - using the SQLiteDatabase API
* to
* version 2 - using Room
*/
@VisibleForTesting
static final Migration MIGRATION_1_2 = new Migration(1, 2) {
@Override
public void migrate(SupportSQLiteDatabase database) {
// Room uses an own database hash to uniquely identify the database
// Since version 1 does not use Room, it doesn't have the database hash associated.
// By implementing a Migration class, we're telling Room that it should use the data
// from version 1 to version 2.
// If no migration is provided, then the tables will be dropped and recreated.
// Since we didn't alter the table, there's nothing else to do here.
}
};
GitHub source code:
https://github.com/IvanBean/ITBon2018/tree/day16-room-migration
Reference:
Understanding migrations with Room
Android Architecture Components: Room — Migration