نقل قاعدة بيانات الغرفة

أثناء إضافة الميزات وتغييرها في تطبيقك، عليك تعديل كيان الغرفة. والفئات وجداول قاعدة البيانات الأساسية لتعكس هذه التغييرات. من المهم بيانات المستخدم المتوفرة بالفعل في قاعدة البيانات على الجهاز عند تشغيل تطبيق يؤدي تحديث إلى تغيير مخطط قاعدة البيانات.

تتيح الغرفة كلا الخيارين التلقائيين واليدويين لنقل البيانات بشكل تدريجي. تتوافق عمليات نقل البيانات التلقائية مع معظم التغييرات الأساسية في المخطط، ولكن قد تحتاج إلى إجراء ما يلي: تحديد مسارات نقل البيانات يدويًا لإجراء تغييرات أكثر تعقيدًا.

عمليات نقل البيانات المبرمَجة

للإعلان عن عملية نقل مبرمَج بين نسختَين من قاعدة البيانات، أضِف @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 {
  ...
}

مواصفات نقل البيانات التلقائي

إذا رصدت الغرفة تغييرات غامضة في المخطط ولا يمكنها إنشاء بدون مزيد من المدخلات، فإنه يعرض خطأ وقت التجميع ويسألك تنفيذ 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، سيتم طلبها من الغرفة بعد يتم اكتمال النقل التلقائي.

عمليات نقل البيانات اليدوية

في الحالات التي تتضمن فيها عملية نقل البيانات تغييرات معقّدة في المخطط، قد لا يتم إنشاء مسار ترحيل مناسب تلقائيًا. على سبيل المثال، إذا فقررت تقسيم البيانات الموجودة في جدول إلى جدولين، فلن تستطيع الغرفة معرفة كيفية إجراء هذا التقسيم. في مثل هذه الحالات، يجب عليك يدويًا تحديد مسار ترحيل من خلال تنفيذ صف واحد (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 الدليل وترحيلها.

اختبار عمليات نقل البيانات

غالبًا ما تكون عمليات النقل معقدة، ويمكن أن يتسبب تحديد الترحيل بشكل غير صحيح في تعطُّل تطبيقك. للحفاظ على استقرار تطبيقك، اختبِر وعمليات الترحيل. توفّر الغرفة عناصر Maven من "room-testing" للمساعدة في لعملية اختبار لكل من عمليات النقل التلقائية واليدوية. لهذا العنصر إلى يجب عليك أولاً تصدير مخطط قاعدة البيانات لديك.

تصدير المخططات

يمكن للغرفة تصدير معلومات مخطّط قاعدة البيانات إلى ملف JSON عند التجميع. الوقت. تمثّل ملفات JSON التي تم تصديرها سجلّ مخطّط قاعدة البيانات. متجر هذه الملفات في نظام التحكم في الإصدار بحيث يمكن لـ Room إنشاء إصدارات أقل من قاعدة البيانات لأغراض الاختبار ولتمكين إنشاء الترحيل التلقائي.

ضبط موقع المخطط باستخدام مكوّن Gradle الإضافي للغرفة

إذا كنت تستخدم الإصدار 2.6.0 من الغرفة أو إصدارًا أحدث، يمكنك تطبيق المكوّن الإضافي لنظام Gradle المتوافق مع الغرف واستخدام room لتحديد دليل المخطط.

Groovy

plugins {
  id 'androidx.room'
}

room {
  schemaDirectory "$projectDir/schemas"
}

Kotlin

plugins {
  id("androidx.room")
}

room {
  schemaDirectory("$projectDir/schemas")
}

إذا كان مخطط قاعدة البيانات يختلف بناءً على الصيغة أو النكهة أو الإصدار النوع، يجب تحديد مواقع مختلفة باستخدام schemaDirectory() التكوين عدة مرات، ولكل منها variantMatchName كأول الوسيطة. ويمكن أن يطابق كل إعداد خيارًا واحدًا أو أكثر استنادًا إلى خيارات مقارنةً باسم الصيغة

تأكَّد من أنّ هذه المعلومات شاملة وتشمل جميع خيارات المنتج. يمكنك أيضًا تضمين schemaDirectory() بدون variantMatchName للتعامل مع خيارات المنتجات غير المطابقة. بأي من التهيئات الأخرى. على سبيل المثال، في تطبيق يحتوي على إصدارين النكهة demo وfull ونوعان من التصميم 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")
}

ضبط موقع المخطط باستخدام خيار معالج التعليقات التوضيحية

إذا كنت تستخدم الإصدار 2.5.2 أو إصدارًا أقدم من الغرفة، أو إذا كنت لا تستخدم مكوّن Gradle الإضافي للغرفة، اضبط موقع المخطط باستخدام room.schemaLocation معالج التعليقات التوضيحية.

تُستخدم الملفات في هذا الدليل كمدخلات ومخرجات لبعض مهام Gradle. ومن أجل صحة وأداء الإصدارات الإضافية والمخزنة مؤقتًا، يجب استخدام من غرادل CommandLineArgumentProvider لإبلاغ 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 من الغرفة في الاختبار الإضافية وإضافة موقع المخطط الذي تم تصديره كمجلد أصل:

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.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 من واجهة المستخدم، حتى يتمكّن من إدارة قواعد البيانات التي تم إنشاؤها.

يوضّح المثال التالي اختبار عملية نقل بيانات واحدة:

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 للاسترخاء في هناك بعض البدائل لـ fallbackToDestructiveMigration():

  • إذا كانت إصدارات معيّنة من سجلّ المخططات تتسبب في أخطاء لا يمكنك حلها مع مسارات الترحيل، واستخدام fallbackToDestructiveMigrationFrom() بدلاً من ذلك. تشير هذه الطريقة إلى أنك تريد من الغرفة العودة إلى الحالة المدمرة. إعادة التحميل فقط عند نقل البيانات من إصدارات معيّنة.
  • إذا كنت تريد من "غرفة" العودة إلى وضع الترفيه المدمر فقط عند نقل البيانات من إصدار قاعدة بيانات أعلى إلى إصدار أقل، استخدم fallbackToDestructiveMigrationOnDowngrade() بدلاً من ذلك.

التعامل مع القيم التلقائية للعمود عند الترقية إلى الغرفة 2.2.0

في الغرفة 2.2.0 والإصدارات الأحدث، يمكنك تحديد قيمة تلقائية للعمود باستخدام التعليق التوضيحي @ColumnInfo(defaultValue = "...") وفي الإصدارات الأقل من 2.2.0، تكون الطريقة الوحيدة لتحديد قيمة تلقائية من خلال تعريفه مباشرةً في عبارة SQL تم تنفيذها، مما يؤدي إلى إنشاء القيمة الافتراضية التي لا تعرفها الغرفة. هذا يعني أنه إذا كانت قاعدة البيانات تم إنشاؤه في الأصل باستخدام إصدار Room أقل من 2.2.0، ما يؤدي إلى ترقية تطبيقك إلى استخدام الغرفة 2.2.0 قد تتطلب منك توفير مسار نقل بيانات خاص القيم التلقائية الحالية التي حدّدتها بدون استخدام واجهات برمجة تطبيقات الغرف.

على سبيل المثال، لنفترض أنّ الإصدار 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");
    }
};