เมื่อคุณเพิ่มและเปลี่ยนฟีเจอร์ในแอป คุณต้องแก้ไขคลาสเอนทิตี Room และตารางฐานข้อมูลเบื้องหลังเพื่อให้สอดคล้องกับการเปลี่ยนแปลงเหล่านี้ สิ่งสำคัญคือต้องเก็บรักษาข้อมูลผู้ใช้ที่อยู่ในฐานข้อมูลในอุปกรณ์ไว้เมื่อการอัปเดตแอปเปลี่ยนสคีมาของฐานข้อมูล
Room รองรับทั้งตัวเลือกอัตโนมัติและตัวเลือกด้วยตนเองสำหรับการย้ายข้อมูลแบบเพิ่มทีละเวอร์ชัน การย้ายข้อมูลอัตโนมัติใช้ได้กับการเปลี่ยนแปลงสคีมาพื้นฐานส่วนใหญ่ แต่คุณอาจต้องกำหนดเส้นทางการย้ายข้อมูลด้วยตนเองสำหรับการเปลี่ยนแปลงที่ซับซ้อนมากขึ้น
การย้ายข้อมูลอัตโนมัติ
หากต้องการประกาศการย้ายข้อมูลอัตโนมัติระหว่างฐานข้อมูล 2 เวอร์ชัน ให้เพิ่มคำอธิบายประกอบ
@AutoMigration ลงใน
พร็อพเพอร์ตี้ autoMigrations
ใน @Databaseดังนี้
Kotlin
// Database class before the version update. @Database( version = 1, entities = [User::class] ) abstract class AppDatabase : RoomDatabase() { ... } // Database class after the version update. @Database( version = 2, entities = [User::class], autoMigrations = [ AutoMigration (from = 1, to = 2) ] ) abstract class AppDatabase : RoomDatabase() { ... }
Java
// Database class before the version update. @Database( version = 1, entities = {User.class} ) public abstract class AppDatabase extends RoomDatabase { ... } // Database class after the version update. @Database( version = 2, entities = {User.class}, autoMigrations = { @AutoMigration (from = 1, to = 2) } ) public abstract class AppDatabase extends RoomDatabase { ... }
ข้อกำหนดของการย้ายข้อมูลอัตโนมัติ
หาก Room ตรวจพบการเปลี่ยนแปลงสคีมาที่ไม่ชัดเจนและไม่สามารถสร้าง
แผนการย้ายข้อมูลได้หากไม่มีข้อมูลเพิ่มเติม ระบบจะแสดงข้อผิดพลาดในเวลาคอมไพล์และขอให้คุณ
ใช้
AutoMigrationSpec
โดยส่วนใหญ่แล้วข้อผิดพลาดนี้จะเกิดขึ้นเมื่อการย้ายข้อมูลเกี่ยวข้องกับสิ่งใดสิ่งหนึ่งต่อไปนี้
- การลบหรือเปลี่ยนชื่อตาราง
- การลบหรือเปลี่ยนชื่อคอลัมน์
คุณสามารถใช้ AutoMigrationSpec เพื่อให้ข้อมูลเพิ่มเติมแก่ Room ที่จำเป็นต่อการสร้างเส้นทางการย้ายข้อมูลอย่างถูกต้อง กำหนดคลาสคงที่ที่ใช้ AutoMigrationSpec ในคลาส RoomDatabase และใส่คำอธิบายประกอบด้วยคำอธิบายประกอบอย่างน้อย 1 รายการต่อไปนี้
หากต้องการใช้การติดตั้งใช้งาน AutoMigrationSpec สำหรับการย้ายข้อมูลอัตโนมัติ ให้ตั้งค่าพร็อพเพอร์ตี้ spec ในคำอธิบายประกอบ @AutoMigration ที่เกี่ยวข้องดังนี้
Kotlin
@Database( version = 2, entities = [User::class], autoMigrations = [ AutoMigration ( from = 1, to = 2, spec = AppDatabase.MyAutoMigration::class ) ] ) abstract class AppDatabase : RoomDatabase() { @RenameTable(fromTableName = "User", toTableName = "AppUser") class MyAutoMigration : AutoMigrationSpec ... }
Java
@Database( version = 2, entities = {AppUser.class}, autoMigrations = { @AutoMigration ( from = 1, to = 2, spec = AppDatabase.MyAutoMigration.class ) } ) public abstract class AppDatabase extends RoomDatabase { @RenameTable(fromTableName = "User", toTableName = "AppUser") static class MyAutoMigration implements AutoMigrationSpec { } ... }
หากแอปต้องทำงานเพิ่มเติมหลังจากที่การย้ายข้อมูลอัตโนมัติเสร็จสมบูรณ์แล้ว คุณ
สามารถใช้
onPostMigrate()ได้
หากคุณใช้เมธอดนี้ใน AutoMigrationSpec Room จะเรียกเมธอดนี้หลังจากที่การย้ายข้อมูลอัตโนมัติเสร็จสมบูรณ์
การย้ายข้อมูลด้วยตนเอง
ในกรณีที่การย้ายข้อมูลเกี่ยวข้องกับการเปลี่ยนแปลงสคีมาที่ซับซ้อน Room อาจไม่สามารถสร้างเส้นทางการย้ายข้อมูลที่เหมาะสมโดยอัตโนมัติ ตัวอย่างเช่น หากคุณตัดสินใจแยกข้อมูลในตารางออกเป็น 2 ตาราง Room จะไม่ทราบวิธีแยกข้อมูล ในกรณีเช่นนี้ คุณต้องกำหนดเส้นทางการย้ายข้อมูลด้วยตนเองโดยใช้คลาส
Migration
คลาส Migration จะกำหนดเส้นทางการย้ายข้อมูลระหว่าง
startVersion กับ endVersion อย่างชัดเจนโดยการลบล้าง
Migration.migrate()
เมธอด เพิ่มคลาส Migration ลงในตัวสร้างฐานข้อมูลโดยใช้
เมธอด
addMigrations()
ดังนี้
Kotlin
val MIGRATION_1_2 = object : Migration(1, 2) { override fun migrate(database: SupportSQLiteDatabase) { database.execSQL("CREATE TABLE `Fruit` (`id` INTEGER, `name` TEXT, " + "PRIMARY KEY(`id`))") } } val MIGRATION_2_3 = object : Migration(2, 3) { override fun migrate(database: SupportSQLiteDatabase) { database.execSQL("ALTER TABLE Book ADD COLUMN pub_year INTEGER") } } Room.databaseBuilder(applicationContext, MyDb::class.java, "database-name") .addMigrations(MIGRATION_1_2, MIGRATION_2_3).build()
Java
static final Migration MIGRATION_1_2 = new Migration(1, 2) { @Override public void migrate(SupportSQLiteDatabase database) { database.execSQL("CREATE TABLE `Fruit` (`id` INTEGER, " + "`name` TEXT, PRIMARY KEY(`id`))"); } }; static final Migration MIGRATION_2_3 = new Migration(2, 3) { @Override public void migrate(SupportSQLiteDatabase database) { database.execSQL("ALTER TABLE Book " + " ADD COLUMN pub_year INTEGER"); } }; Room.databaseBuilder(getApplicationContext(), MyDb.class, "database-name") .addMigrations(MIGRATION_1_2, MIGRATION_2_3).build();
เมื่อกำหนดเส้นทางการย้ายข้อมูล คุณสามารถใช้การย้ายข้อมูลอัตโนมัติสำหรับบางเวอร์ชันและการย้ายข้อมูลด้วยตนเองสำหรับเวอร์ชันอื่นๆ หากคุณกำหนดทั้งการย้ายข้อมูลอัตโนมัติและการย้ายข้อมูลด้วยตนเองสำหรับเวอร์ชันเดียวกัน Room จะใช้การย้ายข้อมูลด้วยตนเอง
ทดสอบการย้ายข้อมูล
การย้ายข้อมูลมักจะซับซ้อน และการย้ายข้อมูลที่กำหนดไว้อย่างไม่ถูกต้องอาจทำให้แอปขัดข้อง ทดสอบการย้ายข้อมูลเพื่อรักษาเสถียรภาพของแอป Room มีอาร์ติแฟกต์ Maven room-testing เพื่อช่วยในกระบวนการทดสอบสำหรับการย้ายข้อมูลทั้งแบบอัตโนมัติและแบบด้วยตนเอง คุณต้องส่งออกสคีมาของฐานข้อมูลก่อนเพื่อให้ใช้งานอาร์ติแฟกต์นี้ได้
ส่งออกสคีมา
Room สามารถส่งออกข้อมูลสคีมาของฐานข้อมูลเป็นไฟล์ JSON ในเวลาคอมไพล์ ไฟล์ JSON ที่ส่งออกจะแสดงประวัติสคีมาของฐานข้อมูล จัดเก็บไฟล์เหล่านี้ไว้ในระบบควบคุมเวอร์ชันเพื่อให้ Room สร้างฐานข้อมูลเวอร์ชันต่ำกว่าเพื่อวัตถุประสงค์ในการทดสอบและเปิดใช้การสร้างการย้ายข้อมูลอัตโนมัติได้
ตั้งค่าตำแหน่งสคีมาโดยใช้ปลั๊กอิน Room Gradle
หากใช้ Room เวอร์ชัน 2.6.0 ขึ้นไป คุณสามารถใช้
ปลั๊กอิน Room Gradle และใช้ส่วนขยาย
room เพื่อระบุไดเรกทอรีสคีมาได้
ดึงดูด
plugins {
id 'androidx.room'
}
room {
schemaDirectory "$projectDir/schemas"
}
Kotlin
plugins {
id("androidx.room")
}
room {
schemaDirectory("$projectDir/schemas")
}
หากสคีมาของฐานข้อมูลแตกต่างกันไปตามตัวแปร เวอร์ชัน หรือประเภทบิลด์ คุณต้องระบุตำแหน่งต่างๆ โดยใช้การกำหนดค่า schemaDirectory() หลายครั้ง โดยแต่ละครั้งจะมี variantMatchName เป็นอาร์กิวเมนต์แรก การกำหนดค่าแต่ละรายการสามารถจับคู่กับตัวแปรอย่างน้อย 1 รายการได้โดยอิงตามการเปรียบเทียบอย่างง่ายกับชื่อตัวแปร
ตรวจสอบว่าการกำหนดค่าเหล่านี้ครอบคลุมตัวแปรทั้งหมด นอกจากนี้ คุณยังสามารถใส่ schemaDirectory() โดยไม่มี variantMatchName เพื่อจัดการตัวแปรที่การกำหนดค่าอื่นๆ ไม่ได้จับคู่ไว้ ตัวอย่างเช่น ในแอปที่มีเวอร์ชันบิลด์ 2 เวอร์ชัน ได้แก่ demo และ full และประเภทบิลด์ 2 ประเภท ได้แก่ debug และ release การกำหนดค่าต่อไปนี้เป็นการกำหนดค่าที่ถูกต้อง
ดึงดูด
room {
// 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
room {
// 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")
}
ตั้งค่าตำแหน่งสคีมาโดยใช้ตัวเลือกโปรเซสเซอร์สำหรับคำอธิบายประกอบ
หากใช้ Room เวอร์ชัน 2.5.2 หรือต่ำกว่า หรือไม่ได้ใช้ปลั๊กอิน Room Gradle ให้ตั้งค่าตำแหน่งสคีมาโดยใช้ตัวเลือกโปรเซสเซอร์สำหรับคำอธิบายประกอบ room.schemaLocation
ระบบจะใช้ไฟล์ในไดเรกทอรีนี้เป็นอินพุตและเอาต์พุตสำหรับงาน Gradle บางอย่าง
คุณต้องใช้
Gradle's
CommandLineArgumentProvider
เพื่อแจ้งให้ Gradle ทราบเกี่ยวกับไดเรกทอรีนี้เพื่อให้การสร้างแบบเพิ่มทีละเวอร์ชันและการสร้างที่แคชไว้ถูกต้องและมีประสิทธิภาพ
ขั้นแรก ให้คัดลอกคลาส RoomSchemaArgProvider ที่แสดงด้านล่างลงในไฟล์บิลด์ Gradle ของโมดูล เมธอด asArguments() ในคลาสตัวอย่างจะส่ง room.schemaLocation=${schemaDir.path} ไปยัง KSP หากคุณใช้ KAPT และ javac ให้เปลี่ยนค่านี้เป็น -Aroom.schemaLocation=${schemaDir.path} แทน
ดึงดูด
class RoomSchemaArgProvider implements CommandLineArgumentProvider {
@InputDirectory
@PathSensitive(PathSensitivity.RELATIVE)
File schemaDir
RoomSchemaArgProvider(File schemaDir) {
this.schemaDir = schemaDir
}
@Override
Iterable<String> asArguments() {
// Note: If you're using KAPT and javac, change the line below to
// return ["-Aroom.schemaLocation=${schemaDir.path}".toString()].
return ["room.schemaLocation=${schemaDir.path}".toString()]
}
}
Kotlin
class RoomSchemaArgProvider(
@get:InputDirectory
@get:PathSensitive(PathSensitivity.RELATIVE)
val schemaDir: File
) : CommandLineArgumentProvider {
override fun asArguments(): Iterable<String> {
// Note: If you're using KAPT and javac, change the line below to
// return listOf("-Aroom.schemaLocation=${schemaDir.path}").
return listOf("room.schemaLocation=${schemaDir.path}")
}
}
จากนั้นกำหนดค่าตัวเลือกการคอมไพล์ให้ใช้ RoomSchemaArgProvider กับไดเรกทอรีสคีมาที่ระบุดังนี้
ดึงดูด
// For KSP, configure using KSP extension:
ksp {
arg(new RoomSchemaArgProvider(new File(projectDir, "schemas")))
}
// For javac or KAPT, configure using android DSL:
android {
...
defaultConfig {
javaCompileOptions {
annotationProcessorOptions {
compilerArgumentProviders(
new RoomSchemaArgProvider(new File(projectDir, "schemas"))
)
}
}
}
}
Kotlin
// For KSP, configure using KSP extension:
ksp {
arg(RoomSchemaArgProvider(File(projectDir, "schemas")))
}
// For javac or KAPT, configure using android DSL:
android {
...
defaultConfig {
javaCompileOptions {
annotationProcessorOptions {
compilerArgumentProviders(
RoomSchemaArgProvider(File(projectDir, "schemas"))
)
}
}
}
}
ทดสอบการย้ายข้อมูลครั้งเดียว
ก่อนที่จะทดสอบการย้ายข้อมูล คุณต้องเพิ่มอาร์ติแฟกต์ Maven androidx.room:room-testing จาก Room ลงในการขึ้นต่อกันของการทดสอบ และเพิ่มตำแหน่งสคีมาที่ส่งออกเป็นโฟลเดอร์เนื้อหาดังนี้
build.gradle
ดึงดูด
android { ... sourceSets { // Adds exported schema location as test app assets. androidTest.assets.srcDirs += files("$projectDir/schemas".toString()) } } dependencies { ... androidTestImplementation "androidx.room:room-testing:2.8.4" }
Kotlin
android { ... sourceSets { // Adds exported schema location as test app assets. getByName("androidTest").assets.srcDir("$projectDir/schemas") } } dependencies { ... testImplementation("androidx.room:room-testing:2.8.4") }
แพ็กเกจการทดสอบมีคลาส
MigrationTestHelper
ซึ่งสามารถอ่านไฟล์สคีมาที่ส่งออกได้ นอกจากนี้ แพ็กเกจยังใช้อินเทอร์เฟซ
JUnit4
TestRule
จึงสามารถจัดการฐานข้อมูลที่สร้างขึ้นได้
ตัวอย่างต่อไปนี้แสดงการทดสอบการย้ายข้อมูลครั้งเดียว
Kotlin
@RunWith(AndroidJUnit4::class) class MigrationTest { private val TEST_DB = "migration-test" @get: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 { // Database has schema version 1. Insert some data using SQL queries. // You can't 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. } }
Java
@RunWith(AndroidJUnit4.class) public class MigrationTest { private static final String TEST_DB = "migration-test"; @Rule public MigrationTestHelper helper; public MigrationTest() { helper = new MigrationTestHelper(InstrumentationRegistry.getInstrumentation(), MigrationDb.class.getCanonicalName(), new FrameworkSQLiteOpenHelperFactory()); } @Test public void migrate1To2() throws IOException { SupportSQLiteDatabase db = helper.createDatabase(TEST_DB, 1); // Database has schema version 1. Insert some data using SQL queries. // You can't use DAO classes because they expect the latest schema. db.execSQL(...); // Prepare for the next version. db.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. } }
ทดสอบการย้ายข้อมูลทั้งหมด
แม้ว่าคุณจะทดสอบการย้ายข้อมูลแบบเพิ่มทีละเวอร์ชันครั้งเดียวได้ แต่เราขอแนะนำให้คุณรวมการทดสอบที่ครอบคลุมการย้ายข้อมูลทั้งหมดที่กำหนดไว้สำหรับฐานข้อมูลของแอป ซึ่งจะช่วยให้มั่นใจว่าอินสแตนซ์ฐานข้อมูลที่สร้างขึ้นล่าสุดและอินสแตนซ์เก่าที่ทำตามเส้นทางการย้ายข้อมูลที่กำหนดไว้จะไม่แตกต่างกัน
ตัวอย่างต่อไปนี้แสดงการทดสอบการย้ายข้อมูลทั้งหมดที่กำหนดไว้
Kotlin
@RunWith(AndroidJUnit4::class) class MigrationTest { private val TEST_DB = "migration-test" // Array of all migrations. private val ALL_MIGRATIONS = arrayOf( MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4) @get:Rule val helper: MigrationTestHelper = MigrationTestHelper( InstrumentationRegistry.getInstrumentation(), AppDatabase::class.java.canonicalName, FrameworkSQLiteOpenHelperFactory() ) @Test @Throws(IOException::class) fun migrateAll() { // Create earliest version of the database. helper.createDatabase(TEST_DB, 1).apply { close() } // Open latest version of the database. Room validates the schema // once all migrations execute. Room.databaseBuilder( InstrumentationRegistry.getInstrumentation().targetContext, AppDatabase::class.java, TEST_DB ).addMigrations(*ALL_MIGRATIONS).build().apply { openHelper.writableDatabase.close() } } }
Java
@RunWith(AndroidJUnit4.class) public class MigrationTest { private static final String TEST_DB = "migration-test"; @Rule public MigrationTestHelper helper; public MigrationTest() { helper = new MigrationTestHelper(InstrumentationRegistry.getInstrumentation(), AppDatabase.class.getCanonicalName(), new FrameworkSQLiteOpenHelperFactory()); } @Test public void migrateAll() throws IOException { // Create earliest version of the database. SupportSQLiteDatabase db = helper.createDatabase(TEST_DB, 1); db.close(); // Open latest version of the database. Room validates the schema // once all migrations execute. AppDatabase appDb = Room.databaseBuilder( InstrumentationRegistry.getInstrumentation().getTargetContext(), AppDatabase.class, TEST_DB) .addMigrations(ALL_MIGRATIONS).build(); appDb.getOpenHelper().getWritableDatabase(); appDb.close(); } // Array of all migrations. private static final Migration[] ALL_MIGRATIONS = new Migration[]{ MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4}; }
จัดการเส้นทางการย้ายข้อมูลที่ขาดหายไปอย่างเหมาะสม
หาก Room ไม่พบเส้นทางการย้ายข้อมูลเพื่ออัปเกรดฐานข้อมูลที่มีอยู่ในอุปกรณ์เป็นเวอร์ชันปัจจุบัน จะเกิดIllegalStateException หากคุณยอมรับที่จะสูญเสียข้อมูลที่มีอยู่เมื่อไม่มีเส้นทางการย้ายข้อมูล ให้เรียกใช้เมธอดตัวสร้าง fallbackToDestructiveMigration() เมื่อสร้างฐานข้อมูลดังนี้
Kotlin
Room.databaseBuilder(applicationContext, MyDb::class.java, "database-name") .fallbackToDestructiveMigration() .build()
Java
Room.databaseBuilder(getApplicationContext(), MyDb.class, "database-name") .fallbackToDestructiveMigration() .build();
เมธอดนี้จะบอกให้ Room สร้างตารางในฐานข้อมูลของแอปขึ้นมาใหม่แบบทำลายล้างเมื่อต้องทำการย้ายข้อมูลแบบเพิ่มทีละเวอร์ชันและไม่มีเส้นทางการย้ายข้อมูลที่กำหนดไว้
หากคุณต้องการให้ Room กลับไปสร้างใหม่แบบทำลายล้างในบางสถานการณ์เท่านั้น คุณสามารถใช้ตัวเลือกอื่นแทน fallbackToDestructiveMigration() ได้ดังนี้
- หากประวัติสคีมาบางเวอร์ชันทำให้เกิดข้อผิดพลาดที่คุณแก้ไขไม่ได้
ด้วยเส้นทางการย้ายข้อมูล ให้ใช้
fallbackToDestructiveMigrationFrom()แทน เมธอดนี้จะระบุว่าคุณต้องการให้ Room กลับไปสร้างใหม่แบบทำลายล้างเมื่อย้ายข้อมูลจากบางเวอร์ชันเท่านั้น - หากต้องการให้ Room กลับไปสร้างใหม่แบบทำลายล้างเมื่อย้ายข้อมูล
จากฐานข้อมูลเวอร์ชันสูงกว่าเป็นเวอร์ชันต่ำกว่าเท่านั้น ให้ใช้
fallbackToDestructiveMigrationOnDowngrade()แทน
จัดการค่าเริ่มต้นของคอลัมน์เมื่ออัปเกรดเป็น Room 2.2.0
ใน Room 2.2.0 ขึ้นไป คุณสามารถกำหนดค่าเริ่มต้นสำหรับคอลัมน์ได้โดยใช้
คำอธิบายประกอบ
@ColumnInfo(defaultValue = "...")
ในเวอร์ชันต่ำกว่า 2.2.0 วิธีเดียวที่จะกำหนดค่าเริ่มต้นสำหรับคอลัมน์คือการกำหนดค่าเริ่มต้นโดยตรงในคำสั่ง SQL ที่ดำเนินการ ซึ่งจะสร้างค่าเริ่มต้นที่ Room ไม่รู้จัก ซึ่งหมายความว่าหากฐานข้อมูลเดิมสร้างขึ้นโดย Room เวอร์ชันต่ำกว่า 2.2.0 การอัปเกรดแอปให้ใช้ Room 2.2.0 อาจกำหนดให้คุณต้องระบุเส้นทางการย้ายข้อมูลพิเศษสำหรับค่าเริ่มต้นที่มีอยู่ซึ่งคุณกำหนดไว้โดยไม่ได้ใช้ Room API
ตัวอย่างเช่น สมมติว่าฐานข้อมูลเวอร์ชัน 1 กำหนดเอนทิตี Song ดังนี้
Kotlin
// Song entity, database version 1, Room 2.1.0. @Entity data class Song( @PrimaryKey val id: Long, val title: String )
Java
// Song entity, database version 1, Room 2.1.0. @Entity public class Song { @PrimaryKey final long id; final String title; }
สมมติว่าฐานข้อมูลเวอร์ชัน 2 เดียวกันเพิ่มคอลัมน์ NOT NULL ใหม่และกำหนดเส้นทางการย้ายข้อมูลจากเวอร์ชัน 1 เป็นเวอร์ชัน 2 ดังนี้
Kotlin
// Song entity, database version 2, Room 2.1.0. @Entity data class Song( @PrimaryKey val id: Long, val title: String, val tag: String // Added in version 2. ) // Migration from 1 to 2, Room 2.1.0. val MIGRATION_1_2 = object : Migration(1, 2) { override fun migrate(database: SupportSQLiteDatabase) { database.execSQL( "ALTER TABLE Song ADD COLUMN tag TEXT NOT NULL DEFAULT ''") } }
Java
// Song entity, database version 2, Room 2.1.0. @Entity public class Song { @PrimaryKey final long id; final String title; @NonNull final String tag; // Added in version 2. } // Migration from 1 to 2, Room 2.1.0. static final Migration MIGRATION_1_2 = new Migration(1, 2) { @Override public void migrate(SupportSQLiteDatabase database) { database.execSQL( "ALTER TABLE Song ADD COLUMN tag TEXT NOT NULL DEFAULT ''"); } };
การดำเนินการนี้จะทำให้ตารางเบื้องหลังมีการเปลี่ยนแปลงระหว่างการอัปเดตและการติดตั้งแอปใหม่ เนื่องจากค่าเริ่มต้นสำหรับคอลัมน์ tag มีการประกาศไว้ในเส้นทางการย้ายข้อมูลจากเวอร์ชัน 1 เป็นเวอร์ชัน 2 เท่านั้น ผู้ใช้ที่ติดตั้งแอปตั้งแต่เวอร์ชัน 2 เป็นต้นไปจึงไม่มีค่าเริ่มต้นสำหรับ tag ในสคีมาของฐานข้อมูล
ใน Room เวอร์ชันต่ำกว่า 2.2.0 การเปลี่ยนแปลงนี้ไม่มีผลกระทบ อย่างไรก็ตาม หากแอปอัปเกรดในภายหลังให้ใช้ Room 2.2.0 หรือสูงกว่า และเปลี่ยนคลาสเอนทิตี Song ให้มีค่าเริ่มต้นสำหรับ tag โดยใช้คำอธิบายประกอบ @ColumnInfo Room จะเห็นความคลาดเคลื่อนนี้ ซึ่งจะทำให้การตรวจสอบสคีมาล้มเหลว
หากต้องการช่วยให้มั่นใจว่าสคีมาของฐานข้อมูลสอดคล้องกันในผู้ใช้ทุกคนเมื่อมีการประกาศค่าเริ่มต้นของคอลัมน์ในเส้นทางการย้ายข้อมูลก่อนหน้า ให้ทำดังนี้ในครั้งแรกที่คุณอัปเกรดแอปให้ใช้ Room 2.2.0 ขึ้นไป
- ประกาศค่าเริ่มต้นของคอลัมน์ในคลาสเอนทิตีที่เกี่ยวข้องโดยใช้คำอธิบายประกอบ
@ColumnInfo - เพิ่มหมายเลขเวอร์ชันของฐานข้อมูลขึ้น 1
- กำหนดเส้นทางการย้ายข้อมูลไปยังเวอร์ชันใหม่ที่ใช้กลยุทธ์การลบและ สร้างใหม่ เพื่อเพิ่มค่าเริ่มต้นที่จำเป็นลงในคอลัมน์ที่มีอยู่
ตัวอย่างต่อไปนี้แสดงกระบวนการนี้
Kotlin
// Migration from 2 to 3, Room 2.2.0. val MIGRATION_2_3 = object : Migration(2, 3) { override fun migrate(database: SupportSQLiteDatabase) { database.execSQL(""" CREATE TABLE new_Song ( id INTEGER PRIMARY KEY NOT NULL, name TEXT, tag TEXT NOT NULL DEFAULT '' ) """.trimIndent()) database.execSQL(""" INSERT INTO new_Song (id, name, tag) SELECT id, name, tag FROM Song """.trimIndent()) database.execSQL("DROP TABLE Song") database.execSQL("ALTER TABLE new_Song RENAME TO Song") } }
Java
// Migration from 2 to 3, Room 2.2.0. static final Migration MIGRATION_2_3 = new Migration(2, 3) { @Override public void migrate(SupportSQLiteDatabase database) { database.execSQL("CREATE TABLE new_Song (" + "id INTEGER PRIMARY KEY NOT NULL," + "name TEXT," + "tag TEXT NOT NULL DEFAULT '')"); database.execSQL("INSERT INTO new_Song (id, name, tag) " + "SELECT id, name, tag FROM Song"); database.execSQL("DROP TABLE Song"); database.execSQL("ALTER TABLE new_Song RENAME TO Song"); } };