เมื่อเพิ่มและเปลี่ยนฟีเจอร์ในแอป คุณจะต้องแก้ไขคลาสเอนทิตี 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 มี room-testing
Maven Artifact เพื่อช่วยใน
กระบวนการทดสอบสำหรับการย้ายข้อมูลทั้งแบบอัตโนมัติและแบบด้วยตนเอง หากต้องการให้อาร์ติแฟกต์นี้ทำงานได้ คุณต้องส่งออกสคีมาของฐานข้อมูลก่อน
ส่งออกสคีมา
Room สามารถส่งออกข้อมูลสคีมาของฐานข้อมูลเป็นไฟล์ JSON ในเวลาคอมไพล์ได้ ไฟล์ JSON ที่ส่งออกแสดงประวัติสคีมาของฐานข้อมูล จัดเก็บ ไฟล์เหล่านี้ในระบบควบคุมเวอร์ชันเพื่อให้ Room สร้างฐานข้อมูลเวอร์ชันที่ต่ำกว่า เพื่อวัตถุประสงค์ในการทดสอบและเพื่อเปิดใช้การสร้างการย้ายข้อมูลอัตโนมัติ
ตั้งค่าตำแหน่งสคีมาโดยใช้ปลั๊กอิน Gradle ของ Room
หากใช้ Room เวอร์ชัน 2.6.0 ขึ้นไป คุณสามารถใช้ Room Gradle Plugin และใช้ส่วนขยาย room
เพื่อระบุไดเรกทอรีสคีมาได้
Groovy
plugins {
id 'androidx.room'
}
room {
schemaDirectory "$projectDir/schemas"
}
Kotlin
plugins {
id("androidx.room")
}
room {
schemaDirectory("$projectDir/schemas")
}
หากสคีมาฐานข้อมูลแตกต่างกันตามตัวแปร รสชาติ หรือประเภทบิลด์
คุณต้องระบุตำแหน่งที่แตกต่างกันโดยใช้schemaDirectory()
การกำหนดค่าหลายครั้ง โดยแต่ละครั้งจะมีvariantMatchName
เป็นอาร์กิวเมนต์แรก
การกำหนดค่าแต่ละรายการจะจับคู่กับตัวแปรอย่างน้อย 1 รายการได้โดยอิงตามการเปรียบเทียบอย่างง่ายกับชื่อตัวแปร
ตรวจสอบว่ารายการเหล่านี้ครอบคลุมและครอบคลุมตัวแปรทั้งหมด นอกจากนี้ คุณยังใส่
schemaDirectory()
โดยไม่มี variantMatchName
เพื่อจัดการกับตัวแปรที่ไม่ตรงกัน
กับการกำหนดค่าอื่นๆ ได้ด้วย ตัวอย่างเช่น ในแอปที่มี Build Flavor 2 รายการ demo
และ full
รวมถึง Build Type 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 หรือต่ำกว่า หรือไม่ได้ใช้
ปลั๊กอิน Room Gradle ให้ตั้งค่าตำแหน่งสคีมาโดยใช้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 ลงใน
dependencies ของการทดสอบ และเพิ่มตำแหน่งของสคีมาที่ส่งออกเป็นโฟลเดอร์เนื้อหา
build.gradle
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.7.2" }
Kotlin
android { ... sourceSets { // Adds exported schema location as test app assets. getByName("androidTest").assets.srcDir("$projectDir/schemas") } } dependencies { ... testImplementation("androidx.room:room-testing:2.7.2") }
แพ็กเกจการทดสอบมีคลาส
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"); } };