Uygulamanıza özellik ekleyip değiştirdikçe bu değişiklikleri yansıtmak için Room varlığı sınıflarınızı ve temel veritabanı tablolarınızı değiştirmeniz gerekir. Bir uygulama güncellemesi veritabanı şemasını değiştirdiğinde cihazdaki veritabanında bulunan kullanıcı verilerinin korunması önemlidir.
Room, artımlı taşıma için hem otomatik hem de manuel seçenekleri destekler. Otomatik taşıma işlemleri çoğu temel şema değişikliğinde işe yarar ancak daha karmaşık değişiklikler için taşıma yollarını manuel olarak tanımlamanız gerekebilir.
Otomatik taşıma işlemleri
İki veritabanı sürümü arasında otomatik geçiş bildirmek için @Database içindeki autoMigrations özelliğine @AutoMigration ek açıklaması ekleyin:
// 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 }
Otomatik taşıma özellikleri
Room, belirsiz şema değişiklikleri algılarsa ve daha fazla giriş olmadan taşıma planı oluşturamazsa derleme zamanı hatası verir. Bu durumda, AutoMigrationSpec uygulaması sağlamanız gerekir. Bu durum en sık olarak aşağıdaki durumlarda görülür:
- Tabloyu silme veya yeniden adlandırma
- Sütun silme veya yeniden adlandırma
Room'un taşıma yollarını doğru şekilde oluşturması için gereken ek bilgileri sağlamak üzere AutoMigrationSpec kullanabilirsiniz. RoomDatabase sınıfınızda AutoMigrationSpec uygulayan bir sınıf tanımlayın ve bu sınıfı aşağıdakilerden biri veya daha fazlasıyla açıklama ekleyin:
Otomatik taşıma için AutoMigrationSpec uygulamasını kullanmak üzere ilgili @AutoMigration açıklamasında spec özelliğini ayarlayın:
@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
Uygulamanızın otomatik taşıma işlemi tamamlandıktan sonra daha fazla işlem yapması gerekiyorsa onPostMigrate uygulayabilirsiniz. Bu işlevi AutoMigrationSpec uygulamanızda kullanırsanız otomatik taşıma tamamlandıktan sonra Room bu işlevi çağırır.
Manuel taşıma işlemleri
Taşıma işlemi karmaşık şema değişiklikleri içeriyorsa Room, uygun bir taşıma yolu oluşturamayabilir. Örneğin, bir tablodaki verileri iki tabloya bölmeye karar verirseniz Room bu bölme işleminin nasıl yapılacağını belirleyemez. Bu durumlarda, Migration sınıfı uygulayarak manuel olarak bir taşıma yolu tanımlamanız gerekir.
Migration sınıfı, migrate işlevini geçersiz kılarak startVersion ile endVersion arasında bir taşıma yolunu açıkça tanımlar. addMigrations işlevini kullanarak Migration sınıflarınızı veritabanı oluşturucunuza ekleyin:
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()
Taşıma yollarınızı tanımlarken bazı sürümler için otomatik taşımaları, diğerleri için ise manuel taşımaları kullanabilirsiniz. Aynı sürüm için hem otomatik taşıma hem de manuel taşıma tanımlarsanız Room, manuel taşımayı kullanır.
Test taşıma işlemleri
Taşıma işlemleri genellikle karmaşıktır ve yanlış tanımlanmış bir taşıma işlemi, uygulamanızın kilitlenmesine neden olabilir. Uygulamanızın kararlılığını korumak için taşımalarınızı test edin. Room, hem otomatik hem de manuel taşımalar için test sürecine yardımcı olacak bir room3-testing Maven yapısı sağlar. Bu yapının çalışması için önce veritabanınızın şemasını dışa aktarmanız gerekir.
Dışa aktarma şemaları
Room, derleme zamanında veritabanınızın şema bilgilerini bir JSON dosyasına aktarır. Dışa aktarılan JSON dosyaları, veritabanınızın şema geçmişini gösterir. Bu dosyaları sürüm denetim sisteminizde depolayın. Böylece, test için veritabanının daha düşük sürümlerini yeniden oluşturabilir ve otomatik taşıma oluşturmayı destekleyebilirsiniz.
Room Gradle eklentisini kullanarak şema konumunu ayarlama
Şema dizinini belirtmek için Room Gradle eklentisini uygulayın ve room3 uzantısını kullanın.
Modern
plugins {
id 'androidx.room3'
}
room3 {
schemaDirectory "$projectDir/schemas"
}
Kotlin
plugins {
id("androidx.room3")
}
room3 {
schemaDirectory("$projectDir/schemas")
}
Veritabanı şemanız varyanta, lezzete veya derleme türüne göre farklılık gösteriyorsa schemaDirectory yapılandırmasını birden çok kez kullanarak farklı konumlar belirtmeniz gerekir. Her yapılandırmada ilk bağımsız değişken olarak variantMatchName kullanılmalıdır. Her yapılandırma, varyant adıyla basit bir karşılaştırma yapılarak bir veya daha fazla varyantla eşleştirilebilir.
Bunların kapsamlı olduğundan ve tüm varyantları kapsadığından emin olun. Diğer yapılandırmaların hiçbiriyle eşleşmeyen varyantları işlemek için schemaDirectory() içermeyen bir variantMatchName de ekleyebilirsiniz. Örneğin, iki derleme çeşidi (demo ve full) ve iki derleme türü (debug ve release) içeren bir uygulamada aşağıdaki yapılandırmalar geçerlidir:
Modern
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")
}
Ek açıklama işleyicisi seçeneğini kullanarak şema konumunu ayarlama
Room Gradle eklentisini kullanmıyorsanız şema konumunu room.schemaLocation ek açıklama işlemcisi seçeneğini kullanarak ayarlayın.
Gradle, bu dizindeki dosyaları bazı Gradle görevleri için giriş ve çıkış olarak kullanır.
Artımlı ve önbelleğe alınmış derlemelerin doğruluğu ve performansı için Gradle'ı bu dizin hakkında bilgilendirmek üzere Gradle'ın CommandLineArgumentProvider özelliğini kullanmanız gerekir.
Öncelikle aşağıdaki RoomSchemaArgProvider sınıfını modülünüzün Gradle derleme dosyasına kopyalayın. Örnek sınıftaki asArguments işlevi, room.schemaLocation=${schemaDir.path} değerini KSP değerine iletir. KAPT ve javac kullanıyorsanız bu değeri -Aroom.schemaLocation=${schemaDir.path} olarak değiştirin.
Modern
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}")
}
}
Ardından, derleme seçeneklerini RoomSchemaArgProvider ile belirtilen şema dizinini kullanacak şekilde yapılandırın:
Modern
ksp {
arg(new RoomSchemaArgProvider(new File(projectDir, "schemas")))
}
Kotlin
ksp {
arg(RoomSchemaArgProvider(File(projectDir, "schemas")))
}
Tek bir taşıma işlemini test etme
Taşımalarınızı test edebilmek için önce androidx.room3:room3-testing
artifact'i test bağımlılıklarınıza ve dışa aktarılan şemanın konumunu öğe dizini olarak ekleyin:
Modern
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") }
Test paketi, dışa aktarılan şema dosyalarını okuyabilen bir MigrationTestHelper sınıfı sağlar. Paket, oluşturulan veritabanlarını yönetmek için JUnit4
TestRule arayüzünü de uygular.
Aşağıdaki örnekte tek bir taşıma için test gösterilmektedir:
@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() } }
Tüm taşıma işlemlerini test etme
Tek bir artımlı taşıma işlemini test edebilseniz de uygulamanızın veritabanı için tanımlanan tüm taşıma işlemlerini kapsayan bir test eklemeniz gerekir. Bu sayede, tanımlanan taşıma yollarını izleyen önceki bir örnekle yeni oluşturulan bir veritabanı örneği arasında tutarsızlık olmaması sağlanır.
Aşağıdaki örnekte, tanımlanan tüm taşımalar için bir test gösterilmektedir:
@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() } }
Eksik taşıma yollarını sorunsuz bir şekilde ele alma
Room, bir cihazdaki mevcut veritabanını mevcut sürüme yükseltmek için taşıma yolu bulamazsa IllegalStateException oluşur. Bir taşıma yolu eksik olduğunda mevcut verileri kaybetmek kabul edilebilir bir durumsa veritabanını oluştururken fallbackToDestructiveMigration oluşturucu işlevini çağırın:
Room.databaseBuilder<FallbackMigrationDatabase>(applicationContext, "database-name") .fallbackToDestructiveMigration() .build()
Bu işlev, artımlı taşıma yapması gerektiğinde ve tanımlanmış bir taşıma yolu olmadığında Room'u uygulamanızın veritabanındaki tabloları yıkıcı bir şekilde yeniden oluşturacak şekilde yapılandırır.
tüm verileri kalıcı olarak siler.Yalnızca belirli durumlarda yıkıcı yeniden oluşturmaya geri dönmek için fallbackToDestructiveMigration yerine aşağıdaki alternatiflerden birini kullanın:
- Şema geçmişinizin belirli sürümleri, taşıma yollarıyla çözemeyeceğiniz hatalara neden oluyorsa bunun yerine
fallbackToDestructiveMigrationFromkullanın. Bu işlev, Room'un yalnızca belirli sürümlerden geçiş yaparken yıkıcı yeniden oluşturmaya geri dönmesini istediğinizi belirtir. - Room'un yalnızca daha yüksek bir veritabanı sürümünden daha düşük bir sürüme geçiş yaparken yıkıcı yeniden oluşturmaya geri dönmesini istiyorsanız
fallbackToDestructiveMigrationOnDowngradekullanın.