[android] Migrating Room databases - Room 에 대해 알아보자
-
Room 은 Migration class 를 써서 db 변화가 있을 때 migrate 를 할 수 있게 해준다.
Migration class 는 startVersion 과 endVersion 을 명시한다.
runtime 에 Room 은 각각의 Migration class 의 migrate() 함수를 호출해준다.
val MIGRATION_1_2 = object : Migration(1, 2){
override fun migrate(db: SupportSQLiteDatabase){
db.execSQL("CREATE TABLE 'Fruit' ('id' INTEGER, 'name' TEXT, PRIMARY KEY('id'))")
}
}
val MIGRATION_2_3 = object : Migration(2, 3){
override fun migrate(db: SupportSQLiteDatabase){
db.execSQL("ALTER TABLE Book ADD COLUMN pub_year INTEGER")
}
}
Room.databaseBuilder(appContext, MyDb::class.java, "db_name")
.addMigrations(MIGRATION_1_2, MIGRATION_2_3)
.build()
-
migration logic 이 제대로 동작하게 하려면, constants 를 ref 하는 것이 아닌 full query string 으로 가져가는 것이 좋다.
-
migration 이 끝난 후, Room 은 schema 를 검증해서 migration 이 제대로 되었는지를 확인한다.
Room 이 문제를 발견한다면 exception 을 던진다.
Test migrations
-
migration 내용은 쓰는 것이 쉽지 않고, crash 를 내기 쉽다.
앱의 안정성을 위해서 migration 에 대해 직접 test 를 해야 한다.
Room 은 test 를 위해 testing Maven artifact 를 제공한다.
하지만 이를 위해서는 db schema export 가 필요하다.
Export schemas
-
컴파일 후에 Room 은 db schema 정보를 JSON file 로 export 한다.
schema 를 export 하려면, build.gradle 의 annotationProcessorOption 으로 room.schemaLocation 값을 설정해줘야 한다.
android {
...
defaultConfig {
...
javaCompileOptions {
annotationProcessorOptions {
arguments = ["room.schemaLocation":"$projectDir/schemas".toString()]
}
}
}
}
-
이 exported 된 schema 를 version control system 에 포함하는 것이 좋다.
-
migration 을 test 하려면, android.arch.persistence.room:testing Maven artifact 를 dependency 로 추가하고, schema location 을 asset folder 로 두는 것이 좋다.
android {
...
sourceSets {
androidTest.assets.srcDirs += files("$projectDir/schemas".toString())
}
}
-
testing pkg 는 MigrationTestHelper class 를 제공하는데, 이 녀석이 schema file 을 읽을 수 있다.
그리고 이 녀석은 JUnit4 의 TestRule interface 를 구현하여, db 생성을 관리할 수 있다.
@RunWith(AndroidJUnit4::class)
class MigrationTest {
private val TEST_DB = "migration-test"
@Rule
val helper: MigrationTestHelper = MigrationTestHelper(
InstrumentationRegistry.getInstrumentation(),
MigrationDb::class.java.canonicalName,
FrameworkSQLiteOpenHelperFactory()
)
@Test
@Throws(IOException::class)
fun migrate1To2() {
var db = helper.createDatabase(TEST_DB, 1).apply {
// db has schema version 1. insert some data using SQL queries.
// You cannot use DAO classes because they expect the latest schema.
execSQL(...)
// Prepare for the next version.
close()
}
// Re-open the database with version 2 and provide
// MIGRATION_1_2 as the migration process.
db = helper.runMigrationsAndValidate(TEST_DB, 2, true, MIGRATION_1_2)
// MigrationTestHelper automatically verifies the schema changes,
// but you need to validate that the data was migrated properly.
}
}
Gracefully handle missing migration paths
-
db schema update 후, 몇몇 단말에서는 db 를 아직 오래된 schema 형태로 사용할 수도 있다.
Room 이 단말 db 의 upgrading migration 을 찾지 못한다면, IllegalStateException 이 발생한다.
이런 crash 가 발생하지 않게 하려면 fallbackToDestructiveMigration() 을 builder 에 호출해야 한다.
Room.databaseBuilder(appContext, MyDb::class.java, "db-name")
.fallbackToDestructiveMigration()
.build()
이 함수를 호출함으로서, Room 은 migration schema version 이 유실되어 migration 이 실패하는 경우 app db table 을 재생성한다.(destructive recreation) 이 때 영구적으로 모든 데이터가 유실될 수 있음을 명심해야 한다.
-
destructive recreation fallback logic 은 다음 추가적인 option 을 가진다.
fallbackToDestructiveMigrationFrom() 은 특정 버전의 schema 에서 에러가 발생해서 에러를 해결할 수 없을 때 쓰면 된다.
fallbackToDestructiveMigrationOnDowngrade() 는 schema downgrade 를 하려고 할 때 발생할 때 쓰면 된다.
-
참고자료
https://developer.android.com/training/data-storage/room/migrating-db-versions