เมื่อเพิ่มและเปลี่ยนแปลงฟีเจอร์ในแอป คุณจะต้องแก้ไขคลาสเอนทิตี 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
และกำกับเนื้อหาด้วยข้อใดข้อหนึ่งต่อไปนี้
หากต้องการใช้การติดตั้งใช้งาน 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 มีroom-testing
อาร์ติแฟกต์ Maven เพื่อช่วยในกระบวนการทดสอบสำหรับการย้ายข้อมูลทั้งแบบอัตโนมัติและด้วยตนเอง หากต้องการให้อาร์ติแฟกต์นี้ทํางาน คุณต้องส่งออกสคีมาของฐานข้อมูลก่อน
ส่งออกสคีมา
Room สามารถส่งออกข้อมูลสคีมาของฐานข้อมูลเป็นไฟล์ JSON ในเวลาคอมไพล์ ไฟล์ JSON ที่ส่งออกแสดงประวัติสคีมาของฐานข้อมูล จัดเก็บไฟล์เหล่านี้ไว้ในระบบควบคุมเวอร์ชันเพื่อให้ Room สร้างฐานข้อมูลเวอร์ชันที่ต่ำกว่าเพื่อวัตถุประสงค์ในการทดสอบและเปิดใช้การสร้างการย้ายข้อมูลอัตโนมัติ
กำหนดตำแหน่งสคีมาโดยใช้ปลั๊กอิน Gradle ของ Room
หากใช้ Room เวอร์ชัน 2.6.0 ขึ้นไป คุณจะใช้ปลั๊กอิน Gradle ของ Room และใช้ส่วนขยาย room
เพื่อระบุไดเรกทอรีสคีมาได้
Groovy
plugins {
id 'androidx.room'
}
room {
schemaDirectory "$projectDir/schemas"
}
Kotlin
plugins {
id("androidx.room")
}
room {
schemaDirectory("$projectDir/schemas")
}
หากสคีมาฐานข้อมูลแตกต่างกันไปตามตัวแปร เวอร์ชัน หรือประเภทบิลด์ คุณต้องระบุตำแหน่งต่างๆ โดยใช้การกำหนดค่า schemaDirectory()
หลายครั้ง โดยแต่ละรายการจะมี variantMatchName
เป็นอาร์กิวเมนต์แรก การกําหนดค่าแต่ละรายการสามารถจับคู่กับตัวแปรอย่างน้อย 1 รายการโดยอิงตามการเปรียบเทียบกับชื่อตัวแปร
ตรวจสอบว่ารายการเหล่านี้ครอบคลุมทุกตัวแปร นอกจากนี้ คุณยังใส่ schemaDirectory()
ที่ไม่มี variantMatchName
เพื่อจัดการตัวแปรที่การกําหนดค่าอื่นๆ ไม่ตรงกันได้อีกด้วย ตัวอย่างเช่น ในแอปที่มี Flavor ของบิลด์ 2 รายการ demo
และ full
และประเภทบิลด์ 2 รายการ debug
และ release
การกําหนดค่าที่ถูกต้องมีดังนี้
Groovy
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 หรือต่ำกว่า หรือไม่ได้ใช้ปลั๊กอิน Gradle ของ Room ให้ตั้งค่าตำแหน่งสคีมาโดยใช้room.schemaLocation
ตัวเลือกโปรแกรมประมวลผลคำอธิบายประกอบ
ไฟล์ในไดเรกทอรีนี้ใช้เป็นอินพุตและเอาต์พุตสำหรับงาน Gradle บางรายการ
คุณต้องใช้ CommandLineArgumentProvider
ของ Gradle เพื่อแจ้ง Gradle เกี่ยวกับไดเรกทอรีนี้เพื่อให้การบิลด์แบบเพิ่มและแบบแคชมีความถูกต้องและมีประสิทธิภาพ
ก่อนอื่น ให้คัดลอกคลาส RoomSchemaArgProvider
ที่แสดงด้านล่างลงในไฟล์บิลด์ Gradle ของโมดูล เมธอด asArguments()
ในคลาสตัวอย่างส่งค่า room.schemaLocation=${schemaDir.path}
ไปยัง KSP
หากคุณใช้ KAPT
และ
javac
ให้เปลี่ยนค่านี้เป็น -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() {
// 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
กับไดเรกทอรีสคีมาที่ระบุ
Groovy
// 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"))
)
}
}
}
}
ทดสอบการย้ายข้อมูลครั้งเดียว
ก่อนที่จะทดสอบการย้ายข้อมูล ให้เพิ่มandroidx.room:room-testing
อาร์ติแฟกต์ Maven จาก Room ลงในข้อกําหนดของข้อทดสอบ และเพิ่มตําแหน่งของสคีมาซึ่งส่งออกเป็นโฟลเดอร์ชิ้นงาน
Groovy
android { ... sourceSets { // Adds exported schema location as test app assets. androidTest.assets.srcDirs += files("$projectDir/schemas".toString()) } } dependencies { ... androidTestImplementation "androidx.room:room-testing:2.6.1" }
Kotlin
android { ... sourceSets { // Adds exported schema location as test app assets. getByName("androidTest").assets.srcDir("$projectDir/schemas") } } dependencies { ... testImplementation("androidx.room:room-testing:2.6.1") }
แพ็กเกจการทดสอบมีคลาส MigrationTestHelper
ที่อ่านไฟล์สคีมาซึ่งส่งออกได้ นอกจากนี้ แพ็กเกจยังใช้อินเทอร์เฟซ TestRule
ของ JUnit4 เพื่อให้จัดการฐานข้อมูลที่สร้างขึ้นได้
ตัวอย่างต่อไปนี้แสดงการทดสอบสำหรับการย้ายข้อมูลครั้งเดียว
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"); } };