Saat menambahkan dan mengubah fitur dalam aplikasi, Anda harus mengubah class entity Room dan tabel database pokok untuk mencerminkan perubahan ini. Penting untuk menyimpan data pengguna yang sudah ada dalam database pada perangkat saat update aplikasi mengubah skema database.
Room mendukung opsi otomatis dan manual untuk migrasi inkremental. Migrasi otomatis berfungsi untuk sebagian besar perubahan skema dasar, tetapi Anda mungkin perlu menentukan jalur migrasi secara manual untuk perubahan yang lebih kompleks.
Migrasi otomatis
Untuk mendeklarasikan migrasi otomatis antara dua versi database, tambahkan an
@AutoMigration anotasi ke properti autoMigrations di
@Database:
// Database class before the version update. @Database( version = 1, entities = [User::class] ) abstract class AppDatabaseV1 : RoomDatabase() { abstract fun userDao(): UserDao } // Database class after the version update. @Database( version = 2, entities = [User::class], autoMigrations = [ AutoMigration(from = 1, to = 2) ] ) abstract class AppDatabaseV2 : RoomDatabase() { abstract fun userDao(): UserDao }
Spesifikasi migrasi otomatis
Jika Room mendeteksi adanya perubahan skema yang ambigu dan tidak dapat menghasilkan paket migrasi
tanpa input lain, Room akan menampilkan error waktu kompilasi dan Anda harus menyediakan implementasi
AutoMigrationSpec. Masalah ini paling sering terjadi saat migrasi melibatkan salah satu dari hal berikut:
- Menghapus atau mengganti nama tabel.
- Menghapus atau mengganti nama kolom.
Anda dapat menggunakan AutoMigrationSpec untuk memberi informasi tambahan yang diperlukan Room untuk membuat jalur migrasi dengan benar. Tentukan class yang mengimplementasikan AutoMigrationSpec di class RoomDatabase Anda dan beri anotasi dengan satu atau beberapa hal berikut:
Agar dapat menggunakan implementasi AutoMigrationSpec untuk migrasi otomatis, tetapkan properti spec dalam anotasi @AutoMigration yang sesuai:
@Database( version = 2, entities = [User::class], autoMigrations = [ AutoMigration ( from = 1, to = 2, spec = MigrationSpec1To2::class ) ] ) abstract class AppDatabaseWithSpec : RoomDatabase() { abstract fun userDao(): UserDao } @RenameTable(fromTableName = "User", toTableName = "AppUser") internal class MigrationSpec1To2 : AutoMigrationSpec
Jika aplikasi Anda perlu melakukan lebih banyak pekerjaan setelah migrasi otomatis selesai, Anda
dapat mengimplementasikan onPostMigrate. Jika Anda mengimplementasikan fungsi ini di AutoMigrationSpec, Room akan memanggilnya setelah migrasi otomatis selesai.
Migrasi manual
Jika migrasi melibatkan perubahan skema yang kompleks, Room mungkin tidak dapat menghasilkan jalur migrasi yang sesuai secara otomatis. Misalnya, jika Anda memutuskan untuk membagi data dalam tabel menjadi dua tabel, Room tidak dapat menentukan cara melakukan pemisahan ini. Dalam situasi ini, Anda harus menentukan jalur migrasi secara manual dengan mengimplementasikan class Migration.
Class Migration secara eksplisit menentukan jalur migrasi antara startVersion
dan endVersion dengan mengganti fungsi migrate. Tambahkan
class Migration ke builder database menggunakan
addMigrations fungsi:
val MIGRATION_1_2 = object : Migration(1, 2) { override suspend fun migrate(connection: SQLiteConnection) { connection.executeSQL("CREATE TABLE `Fruit` (`id` INTEGER, `name` TEXT, " + "PRIMARY KEY(`id`))") } } val MIGRATION_2_3 = object : Migration(2, 3) { override suspend fun migrate(connection: SQLiteConnection) { connection.executeSQL("ALTER TABLE Book ADD COLUMN pub_year INTEGER") } } Room.databaseBuilder<ManualMigrationDatabase>(applicationContext, "database-name") .addMigrations(MIGRATION_1_2, MIGRATION_2_3) .build()
Saat menentukan jalur migrasi, Anda dapat menggunakan migrasi otomatis untuk sebagian versi dan juga migrasi manual untuk sebagian versi lainnya. Jika Anda menentukan migrasi otomatis dan migrasi manual untuk versi yang sama, Room akan memilih menggunakan migrasi manual.
Menguji migrasi
Migrasi sering kali rumit, dan migrasi yang tidak ditetapkan dengan benar dapat menyebabkan aplikasi Anda error. Untuk mempertahankan stabilitas aplikasi, uji migrasi. Room menyediakan artefak Maven room3-testing untuk membantu proses pengujian migrasi otomatis dan manual. Agar artefak ini berfungsi dengan baik, Anda harus mengekspor skema database terlebih dahulu.
Mengekspor skema
Room mengekspor informasi skema database Anda ke dalam file JSON pada waktu kompilasi. File JSON yang diekspor merepresentasikan histori skema database Anda. Simpan file ini di sistem kontrol versi agar Anda dapat membuat ulang database versi yang lebih rendah untuk pengujian dan mendukung pembuatan migrasi otomatis.
Menetapkan lokasi skema menggunakan Plugin Room Gradle
Untuk menentukan direktori skema, terapkan Plugin Gradle Room dan gunakan
room3 ekstensi.
Groovy
plugins {
id 'androidx.room3'
}
room3 {
schemaDirectory "$projectDir/schemas"
}
Kotlin
plugins {
id("androidx.room3")
}
room3 {
schemaDirectory("$projectDir/schemas")
}
Jika skema database Anda berbeda berdasarkan varian, ragam, atau jenis build, Anda harus menentukan lokasi yang berbeda dengan menggunakan konfigurasi schemaDirectory beberapa kali, masing-masing dengan variantMatchName sebagai argumen pertama. Setiap konfigurasi dapat cocok dengan satu atau beberapa varian berdasarkan perbandingan sederhana dengan nama varian.
Pastikan konfigurasi ini lengkap dan mencakup semua varian. Anda juga dapat menyertakan schemaDirectory() tanpa variantMatchName untuk menangani varian yang tidak cocok dengan konfigurasi lainnya. Misalnya, di aplikasi dengan dua ragam build demo dan full serta dua jenis build debug dan release, konfigurasi berikut valid:
Groovy
room3 {
// Applies to 'demoDebug' only
schemaDirectory "demoDebug", "$projectDir/schemas/demoDebug"
// Applies to 'demoDebug' and 'demoRelease'
schemaDirectory "demo", "$projectDir/schemas/demo"
// Applies to 'demoDebug' and 'fullDebug'
schemaDirectory "debug", "$projectDir/schemas/debug"
// Applies to variants that aren't matched by other configurations.
schemaDirectory "$projectDir/schemas"
}
Kotlin
room3 {
// Applies to 'demoDebug' only
schemaDirectory("demoDebug", "$projectDir/schemas/demoDebug")
// Applies to 'demoDebug' and 'demoRelease'
schemaDirectory("demo", "$projectDir/schemas/demo")
// Applies to 'demoDebug' and 'fullDebug'
schemaDirectory("debug", "$projectDir/schemas/debug")
// Applies to variants that aren't matched by other configurations.
schemaDirectory("$projectDir/schemas")
}
Menetapkan lokasi skema menggunakan opsi pemroses anotasi
Jika Anda tidak menggunakan plugin Gradle Room, tetapkan lokasi skema menggunakan opsi pemroses anotasi room.schemaLocation.
Gradle menggunakan file dalam direktori ini sebagai input dan output untuk beberapa tugas Gradle.
Untuk kebenaran dan performa build inkremental dan yang di-cache, Anda harus menggunakan
Gradle's CommandLineArgumentProvider untuk memberi tahu Gradle tentang
direktori ini.
Pertama, salin class RoomSchemaArgProvider berikut ke dalam file build Gradle modul Anda. Fungsi asArguments di class contoh meneruskan room.schemaLocation=${schemaDir.path} ke KSP. Jika Anda menggunakan KAPT dan javac, ubah nilai ini menjadi -Aroom.schemaLocation=${schemaDir.path}.
Groovy
class RoomSchemaArgProvider implements CommandLineArgumentProvider {
@InputDirectory
@PathSensitive(PathSensitivity.RELATIVE)
File schemaDir
RoomSchemaArgProvider(File schemaDir) {
this.schemaDir = schemaDir
}
@Override
Iterable<String> asArguments() {
return ["room.schemaLocation=${schemaDir.path}".toString()]
}
}
Kotlin
class RoomSchemaArgProvider(
@get:InputDirectory
@get:PathSensitive(PathSensitivity.RELATIVE)
val schemaDir: File
) : CommandLineArgumentProvider {
override fun asArguments(): Iterable<String> {
return listOf("room.schemaLocation=${schemaDir.path}")
}
}
Kemudian, konfigurasi opsi kompilasi untuk menggunakan RoomSchemaArgProvider dengan direktori skema yang ditentukan:
Groovy
ksp {
arg(new RoomSchemaArgProvider(new File(projectDir, "schemas")))
}
Kotlin
ksp {
arg(RoomSchemaArgProvider(File(projectDir, "schemas")))
}
Menguji satu migrasi
Sebelum menguji migrasi, tambahkan artefak androidx.room3:room3-testing ke dependensi pengujian Anda, lalu tambahkan lokasi skema yang diekspor sebagai direktori aset:
Groovy
android { ... sourceSets { // Adds exported schema location as test app assets if not using // the Room Gradle Plugin. androidTest.assets.srcDirs += files("$projectDir/schemas".toString()) } } dependencies { ... androidTestImplementation "androidx.room3:room3-testing:3.0.1" }
Kotlin
android { ... sourceSets { // Adds exported schema location as test app assets if not using // the Room Gradle Plugin. getByName("androidTest").assets.srcDir("$projectDir/schemas") } } dependencies { ... testImplementation("androidx.room3:room3-testing:3.0.1") }
Paket pengujian menyediakan MigrationTestHelper class, yang dapat membaca
file skema yang diekspor. Paket ini juga mengimplementasikan antarmuka JUnit4
TestRule untuk mengelola database yang dibuat.
Contoh berikut menunjukkan pengujian untuk satu migrasi:
@RunWith(AndroidJUnit4::class) class MigrationTest { private val TEST_DB = "migration-test" private val instrumentation = InstrumentationRegistry.getInstrumentation() @get:Rule val helper = MigrationTestHelper( instrumentation = instrumentation, databaseClass = MigrationDb::class, driver = AndroidSQLiteDriver(), file = instrumentation.targetContext.getDatabasePath(TEST_DB), ) @Test fun migrate1To2() = runTest { val connection = helper.createDatabase(1) // Database has schema version 1. Insert some data using SQL queries. // You can't use DAO classes because they expect the latest schema. connection.execSQL("INSERT INTO User (id, name) VALUES (1, 'John Doe')") connection.close() // Re-open the database with version 2 and provide MIGRATION_1_2 val migratedConnection = helper.runMigrationsAndValidate(2, listOf(MIGRATION_1_2)) // MigrationTestHelper automatically verifies the schema changes, // but you need to validate that the data was migrated properly. val hasData = migratedConnection.prepare("SELECT COUNT(*) FROM User").use { it.step() it.getLong(0) > 0 } assertTrue("Expected data was not migrated", hasData) migratedConnection.close() } }
Menguji semua migrasi
Meskipun Anda dapat menguji satu migrasi inkremental, sebaiknya sertakan pengujian yang mencakup semua migrasi yang ditentukan untuk database aplikasi Anda. Hal ini membantu memastikan agar tidak ada perbedaan antara instance database yang baru dibuat dan instance lama yang mengikuti jalur migrasi yang ditentukan.
Contoh berikut menunjukkan pengujian untuk semua migrasi yang ditentukan:
@RunWith(AndroidJUnit4::class) class MigrationTest { private val TEST_DB = "migration-test" private val instrumentation = InstrumentationRegistry.getInstrumentation() // Array of all migrations. private val ALL_MIGRATIONS = arrayOf(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4) @get:Rule val helper: MigrationTestHelper = MigrationTestHelper( instrumentation = instrumentation, databaseClass = MigrationDb::class, driver = AndroidSQLiteDriver(), file = instrumentation.targetContext.getDatabasePath(TEST_DB), ) @Test fun migrateAll() = runTest { // Create earliest version of the database. val connection = helper.createDatabase(1) connection.close() // Create latest version of the database. val db = Room.databaseBuilder<AppDatabase>(instrumentation.targetContext, TEST_DB) .setDriver(AndroidSQLiteDriver()) .addMigrations(*ALL_MIGRATIONS) .build() // Open the database, Room validates the schema once all migrations // execute. db.useReaderConnection { connection -> // Perform additional validation } db.close() } }
Menangani jalur migrasi yang hilang dengan tepat
Jika Room tidak dapat menemukan jalur migrasi untuk mengupgrade database yang sudah ada di
perangkat ke versi saat ini, IllegalStateException akan muncul. Jika
hilangnya data yang ada saat jalur migrasi tidak ada dapat diterima, panggil
fungsi builder fallbackToDestructiveMigration ketika membuat
database:
Room.databaseBuilder<FallbackMigrationDatabase>(applicationContext, "database-name") .fallbackToDestructiveMigration() .build()
Fungsi ini mengonfigurasi Room untuk membuat ulang tabel secara destruktif di database aplikasi Anda ketika perlu melakukan migrasi inkremental dan tidak ada jalur migrasi yang ditentukan.
Untuk kembali ke pembuatan ulang yang destruktif hanya dalam situasi tertentu, gunakan salah satu alternatif berikut untuk fallbackToDestructiveMigration:
- Jika versi tertentu histori skema menyebabkan error yang tidak dapat Anda atasi
dengan jalur migrasi, gunakan
fallbackToDestructiveMigrationFromsebagai gantinya. Fungsi ini menunjukkan bahwa Anda ingin agar Room menggunakan mode pembuatan ulang yang destruktif hanya saat melakukan migrasi dari versi tertentu. - Jika Anda ingin Room menggunakan mode pembuatan ulang yang destruktif hanya saat melakukan migrasi
dari versi database yang lebih baru ke versi yang lebih lama, gunakan
fallbackToDestructiveMigrationOnDowngradesebagai gantinya.