ย้ายข้อมูลฐานข้อมูลห้อง

เมื่อเพิ่มและเปลี่ยนแปลงฟีเจอร์ในแอป คุณจะต้องแก้ไขเอนทิตีห้อง และตารางฐานข้อมูลที่สำคัญเพื่อแสดงการเปลี่ยนแปลงเหล่านี้ เป็นสิ่งสำคัญ เพื่อเก็บรักษาข้อมูลผู้ใช้ที่อยู่ในฐานข้อมูลในอุปกรณ์อยู่แล้วเมื่อแอป อัปเดตเปลี่ยนแปลงสคีมาของฐานข้อมูล

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 เพื่อให้ข้อมูลเพิ่มเติมแก่ห้องแชทได้ ต้องสร้างเส้นทางการย้ายข้อมูลอย่างถูกต้อง กำหนดคลาสแบบคงที่ที่ นำ 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 จะเรียกใช้หลังจาก การย้ายข้อมูลอัตโนมัติเสร็จสมบูรณ์

การย้ายข้อมูลด้วยตนเอง

ในกรณีที่การย้ายข้อมูลเกี่ยวข้องกับการเปลี่ยนแปลงสคีมาที่ซับซ้อน ห้องแชทอาจ สามารถสร้างเส้นทางการย้ายข้อมูลที่เหมาะสมโดยอัตโนมัติ ตัวอย่างเช่น หาก คุณตัดสินใจที่จะแบ่งข้อมูลในตารางให้เป็นสองตาราง ห้องไม่สามารถบอกได้ วิธีทำการแยก ในกรณีเช่นนี้ คุณต้อง กำหนดเส้นทางการย้ายข้อมูลโดยใช้ 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-testing Maven ที่จะช่วยอำนวยความสะดวก ทั้งการย้ายข้อมูลอัตโนมัติและการย้ายข้อมูลด้วยตนเอง สำหรับอาร์ติแฟกต์นี้เพื่อ คุณต้องส่งออกสคีมาของฐานข้อมูลก่อน

ส่งออกสคีมา

ห้องแชทสามารถส่งออกข้อมูลสคีมาของฐานข้อมูลเป็นไฟล์ JSON ขณะคอมไพล์ได้ ไฟล์ JSON ที่ส่งออกจะแสดงประวัติสคีมาของฐานข้อมูล ร้านค้า ไฟล์เหล่านี้ไว้ในระบบควบคุมเวอร์ชัน เพื่อให้ห้องแชทสามารถสร้างเวอร์ชันที่ต่ำกว่า ฐานข้อมูลสำหรับวัตถุประสงค์ในการทดสอบและเปิดใช้การสร้างการย้ายข้อมูลอัตโนมัติ

ตั้งค่าตำแหน่งสคีมาโดยใช้ปลั๊กอิน Room Gradle

หากใช้ห้องแชทเวอร์ชัน 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")
}

ตั้งค่าตำแหน่งของสคีมาโดยใช้ตัวเลือกตัวประมวลผลคำอธิบายประกอบ

หากคุณใช้ห้องแชทเวอร์ชัน 2.5.2 หรือต่ำกว่า หรือไม่ได้ใช้ ปลั๊กอิน Room Gradle ตั้งค่าตำแหน่งสคีมาโดยใช้ room.schemaLocation ตัวประมวลผลคำอธิบายประกอบ

ระบบใช้ไฟล์ในไดเรกทอรีนี้เป็นอินพุตและเอาต์พุตสำหรับงาน Gradle บางงาน เพื่อความถูกต้องและประสิทธิภาพของบิลด์ที่เพิ่มขึ้นและบิลด์ที่แคชไว้ คุณต้องใช้ สำหรับ Gradle 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"))
        )
      }
    }
  }
}

ทดสอบการย้ายข้อมูลรายการเดียว

ก่อนที่คุณจะสามารถทดสอบการย้ายข้อมูล ให้เพิ่ม androidx.room:room-testing อาร์ติแฟกต์ของ Maven จากห้องแชทไปยังการทดสอบ ทรัพยากร Dependency และเพิ่มตำแหน่งของสคีมาที่ส่งออกเป็นโฟลเดอร์ชิ้นงาน โดยทำดังนี้

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.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 ซึ่งอ่านไฟล์สคีมาที่ส่งออกได้ แพ็กเกจยังใช้ 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};
}

จัดการเส้นทางการย้ายข้อมูลที่ขาดหายไปอย่างค่อยเป็นค่อยไป

หากห้องแชทไม่พบเส้นทางการย้ายข้อมูลเพื่ออัปเกรดฐานข้อมูลที่มีอยู่ใน อุปกรณ์เป็นเวอร์ชันปัจจุบัน IllegalStateException เกิดขึ้น ถ้า การที่ข้อมูลที่มีอยู่สูญหายได้ หากเส้นทางการย้ายข้อมูลหายไป ให้เรียก เวลา fallbackToDestructiveMigration() ของเครื่องมือสร้างเมื่อคุณสร้างฐานข้อมูล

Kotlin

Room.databaseBuilder(applicationContext, MyDb::class.java, "database-name")
        .fallbackToDestructiveMigration()
        .build()

Java

Room.databaseBuilder(getApplicationContext(), MyDb.class, "database-name")
        .fallbackToDestructiveMigration()
        .build();

วิธีนี้จะทำให้ Room สร้างตารางขึ้นใหม่ในแอป ฐานข้อมูลเมื่อจำเป็นต้องดำเนินการย้ายข้อมูลเพิ่มและไม่มี เส้นทางการย้ายข้อมูลที่กำหนดไว้

หากคุณแค่ต้องการใช้ห้องเพื่อกลับไปใช้กิจกรรมนันทนาการที่ทำลายล้างในบางสถานการณ์ มีทางเลือกอีก 2-3 อย่างนอกเหนือจาก fallbackToDestructiveMigration() ดังนี้

  • หากประวัติสคีมาบางเวอร์ชันเกิดข้อผิดพลาดที่คุณแก้ไขไม่ได้ กับเส้นทางการย้ายข้อมูล ให้ใช้ fallbackToDestructiveMigrationFrom() แทน วิธีนี้ระบุว่าคุณต้องการเปลี่ยนห้องแชทกลับไปเป็นโหมดที่เป็นอันตราย ใหม่ก็ต่อเมื่อย้ายข้อมูลจากเวอร์ชันที่เฉพาะเจาะจงเท่านั้น
  • หากต้องการให้ห้องแชทกลับไปใช้กิจกรรมนันทนาการที่ก่อให้เกิดความเสียหายกับอุปกรณ์เฉพาะตอนที่ย้ายข้อมูลเท่านั้น จากฐานข้อมูลเวอร์ชันสูงกว่าไปยังเวอร์ชันที่ต่ำกว่า fallbackToDestructiveMigrationOnDowngrade() แทน

จัดการค่าเริ่มต้นของคอลัมน์เมื่ออัปเกรดเป็นห้อง 2.2.0

ในห้องแชท 2.2.0 ขึ้นไป คุณกำหนดค่าเริ่มต้นสำหรับคอลัมน์ได้โดยใช้ คำอธิบายประกอบ @ColumnInfo(defaultValue = "...") ในเวอร์ชันที่ต่ำกว่า 2.2.0 นี่เป็นวิธีเดียวในการกำหนดค่าเริ่มต้นสำหรับ คือการระบุโดยตรงในคำสั่ง SQL ที่ดำเนินการแล้ว ซึ่งจะสร้าง ค่าเริ่มต้นที่ห้องแชทไม่ทราบ ซึ่งหมายความว่าหากฐานข้อมูล เดิมสร้างขึ้นโดยห้องแชทเวอร์ชันต่ำกว่า 2.2.0 ซึ่งจะอัปเกรดแอปเป็น เมื่อใช้ห้อง 2.2.0 คุณอาจต้องระบุเส้นทางการย้ายข้อมูลพิเศษสำหรับ ค่าเริ่มต้นที่มีอยู่ซึ่งคุณได้กำหนดไว้โดยไม่ต้องใช้ 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 ในสคีมาฐานข้อมูล

ในห้องแชทเวอร์ชันต่ำกว่า 2.2.0 ความคลาดเคลื่อนนี้ไม่เป็นอันตราย อย่างไรก็ตาม หาก แอปจะอัปเกรดให้ใช้ห้อง 2.2.0 ขึ้นไปในภายหลังและเปลี่ยนเอนทิตี Song เพื่อรวมค่าเริ่มต้นสำหรับ tag โดยใช้ คำอธิบายประกอบ @ColumnInfo, ห้องแชท ก็จะเห็นความคลาดเคลื่อนนี้ ซึ่งส่งผลให้สคีมาล้มเหลว การตรวจสอบความถูกต้อง

เพื่อช่วยให้สคีมาฐานข้อมูลสอดคล้องกันสำหรับผู้ใช้ทั้งหมดเมื่อคอลัมน์ ระบบจะประกาศค่าเริ่มต้นในเส้นทางการย้ายข้อมูลก่อนหน้านี้ ดังนี้ ครั้งแรกที่คุณอัปเกรดแอปไปใช้ห้อง 2.2.0 ขึ้นไป ให้ทำดังนี้

  1. ประกาศค่าเริ่มต้นของคอลัมน์ในคลาสเอนทิตีที่เกี่ยวข้องโดยใช้ คำอธิบายประกอบ @ColumnInfo รายการ
  2. เพิ่มหมายเลขเวอร์ชันของฐานข้อมูลขึ้น 1
  3. กำหนดเส้นทางการย้ายข้อมูลไปยังเวอร์ชันใหม่ที่ดำเนินการวางและ สร้างกลยุทธ์ใหม่ เพื่อเพิ่มค่าเริ่มต้นที่จำเป็นลงในคอลัมน์ที่มีอยู่

ตัวอย่างต่อไปนี้แสดงถึงกระบวนการนี้

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");
    }
};