2026'da Room veritabanı migrasyonları: tek bir satır kaybetmeden şema değişikliği göndermek
Android'de Room veritabanı migrasyonları için pratik bir rehber — AutoMigration, elle yazılmış Migration nesneleri ve kullanıcılarınız bulmadan önce bir migrasyonu nasıl test edersiniz.
Her yerel-öncelikli uygulama er ya da geç 1. sürümle gönderilmeyen bir şemaya ihtiyaç duyar. Yeni bir sütun, yeniden adlandırılmış bir tablo, bir zamanlar String olan ve Int’e dönüşmesi gereken bir alan. Bu olduğu an, dikkatsiz bir anotasyon uzağınızda kullanıcıların bir sonraki güncellemede tüm verilerini silmek var. Room migrasyonları bunu önlemek için var ve insanların bunlarla yaşadığı acının çoğu, isteğe bağlı gibi görünen ama olmayan kısımları atlamaktan kaynaklanıyor.
Tuzak: fallbackToDestructiveMigration()
Room, @Database sürüm numaranız eşleşen bir migrasyon yolu olmadan yükseldiği an bir IllegalStateException fırlatır. Her Stack Overflow cevabında karşınıza çıkan düzeltme tek satırdır:
Room.databaseBuilder(context, AppDatabase::class.java, "app.db")
.fallbackToDestructiveMigration()
.build()
Bu derlenir, gönderilir ve mükemmel çalışır — debug derlemelerinde, kendi cihazınızda, test verisini kaybetmeyi umursamadığınız yerde. Üretimde şu anlama gelir: bir sonraki sefer şema sürümünü yükselttiğinizde, Room her tabloyu siler ve boş olarak yeniden oluşturur. Granyn için bu, birinin iki yıllık bütçe kaydının, istemediği bir uygulama güncellemesinde sessizce yok olması demek. Ne bir uyarı diyaloğu, ne de geri alma var. Uygulama bir sonraki açıldığında sessizce oluyor. Bu satır debug dışı herhangi bir yapılandırmada varsa, onu bir kolaylık değil, bir hata olarak ele alın.
AutoMigration, insanların sandığından fazlasını kapsıyor
Yaygın durumlarda — varsayılan değerli bir sütun eklemek, bir tablo eklemek veya kaldırmak, bir sütunu yeniden adlandırmak — Room’un @AutoMigration’ı, iki şema anlık görüntüsünden migrasyonu sizin için üretebilir:
@Database(
version = 2,
entities = [Entry::class],
autoMigrations = [
AutoMigration(from = 1, to = 2)
]
)
abstract class AppDatabase : RoomDatabase()
Bu yalnızca şema dışa aktarmayı açtıysanız çalışır (Gradle yapılandırmanızda room.schemaLocation), böylece Room’un karşılaştıracağı her sürüm için bir JSON anlık görüntüsü olur. Bu kurulumu atlarsanız AutoMigration’ın karşılaştıracağı hiçbir şey olmaz ve bunu derleme zamanında öğrenirsiniz, gece yarısı bir çökme raporunda değil. Yeniden adlandırılmış veya silinmiş bir sütun için de Room’a eski ve yeni adları gösteren küçük bir @RenameColumn veya @DeleteColumn sınıfına ihtiyacınız var — niyeti yalnızca bir karşılaştırmadan çıkaramaz.
AutoMigration’ın yetersiz kaldığı yer
Var olan veriyi yeniden şekillendirmesi gereken her şey — bir sütunu ikiye bölmek, saklanan bir String tutarı sente dönüştürmek, yeni bir zorunlu alanı diğer satırlardan geriye doldurmak — AutoMigration’ın kapsamı dışındadır. Bu, etrafından dolaşılacak bir kısıtlama değil; gerçek SQL içeren gerçek bir Migration nesnesine ihtiyacınız olduğunun işaretidir.
Migrasyonu elle yazmak
Elle yazılmış bir Migration, sorumlu olduğunuz bir SQL bloğuyla birlikte bir sürüm çiftidir:
val MIGRATION_2_3 = object : Migration(2, 3) {
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL(
"ALTER TABLE entries ADD COLUMN currency TEXT NOT NULL DEFAULT 'USD'"
)
db.execSQL(
"UPDATE entries SET currency = (SELECT default_currency FROM user_prefs LIMIT 1)"
)
}
}
Bunu veritabanı builder’ında .addMigrations(MIGRATION_2_3) ile varsa AutoMigration’ların yanına kaydedin. SQLite’ın ALTER TABLE’ı sınırlıdır — sütun silme yok, yerinde sütun tipi değiştirme yok — bu yüzden bir sütun eklemenin ötesindeki her şey genellikle klasik üç adımlı dansı gerektirir: istediğiniz şekilde yeni bir tablo oluşturun, veriyi INSERT INTO ... SELECT ile karşıya kopyalayın, eski tabloyu silin, yenisini yeniden adlandırın. Yazmak isteyeceğinizden daha fazla SQL ve tam olarak doğru olması gereken SQL bu, çünkü her cihazda bir sonraki açılışta sessizce bir kez çalışıyor.
Migrasyonu test edin, sadece hedef şemayı değil
Burada en çok hataya yol açan yanlış SQL değil — migrasyonu gerçek, eskimiş bir veritabanına karşı hiç çalıştırmamak. Room’un MigrationTestHelper’ı tam da bunun için var:
@get:Rule
val helper = MigrationTestHelper(
InstrumentationRegistry.getInstrumentation(),
AppDatabase::class.java
)
@Test
fun migrate2To3_preservesExistingRows() {
helper.createDatabase(TEST_DB, 2).apply {
execSQL("INSERT INTO entries (id, amount) VALUES (1, 4200)")
close()
}
helper.runMigrationsAndValidate(TEST_DB, 3, true, MIGRATION_2_3)
}
Bu, sürüm-2 bir veritabanı oluşturur, gerçek bir kullanıcının cihazının görüneceği şekilde tohumlar, sonra migrasyonunuzu buna karşı çalıştırır ve sonuçtaki şemanın Room’un beklediğiyle eşleştiğini doğrular. Üretimde gerçekten yaşanan iki hata modunu yakalar: boş bir veritabanında çalışan ama satırları olan birinde fırlatan bir migrasyon ve SQL’i sorunsuz ama sonuçtaki şeması entity tanımlarınızla eşleşmeyen bir migrasyon (Room bunu sütun sırası ve varsayılan değerlere kadar sıkı biçimde kontrol eder).
Kontrol listesi
Herhangi bir şema sürümü yükseltmesi gönderilmeden önce: şema dışa aktarma açık, her sürüm sıçraması ya bir AutoMigration’a ya da kayıtlı elle yazılmış bir Migration’a sahip, fallbackToDestructiveMigration() test kodu dışında hiçbir yerde görünmüyor ve en az bir MigrationTestHelper testi gerçek satırlar tohumlayıp hayatta kaldıklarını doğruluyor. Bunların hiçbiri heyecan verici bir iş değil. Ama kimsenin fark etmediği bir şema değişikliği ile bir sürümden sonraki sabah “verim kayboldu” mesajlarıyla dolu bir destek kutusu arasındaki fark da bu.
// İlgili okumalar
Günlükten dahası
2026'da Jetpack Glance: verisi hakkında asla yalan söylemeyen bir ana ekran widget'ı
Jetpack Glance widget'ları için pratik bir rehber — durum yönetimi, tıklama aksiyonları ve widget'ları eski veri göstermeye iten güncelleme kotası tuzağı.
OldSchool'un düzen takvimini inşa etmek: doz dokunuşlarını güvenilir bir güne dönüştürmek
Aylık bir düzen takvimi, her gün için tek renkli bir nokta gibi görünür. İşte arkasındaki olay modeli, gün durumu kuralları ve zamanında alım matematiği, cihaz üzerinde.
Subly'yi Geliştirmek: abonelik yenileme tarihlerinin arkasındaki takvim matematiği
Bir aboneliğin sonraki ödeme tarihini tahmin etmek, ay sonu faturalandırmaya, artık yıllara ve deneme sürümü dönüşümlerine çarpana kadar önemsiz görünür. Subly'nin bunu cihaz üzerinde nasıl doğru yaptığı burada.