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

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

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

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

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

عند تحديد مسارات نقل البيانات، يمكنك استخدام عمليات نقل البيانات المبرمَجة لبعض الإصدارات وعمليات نقل البيانات اليدوية لبعض الإصدارات الأخرى. في حال تحديد كل من النقل التلقائي والنقل اليدوي للإصدار نفسه، تستخدم الغرفة عملية النقل اليدوي.

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

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

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

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

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

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

رائع

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:

رائع

room {
  // Applies to 'demoDebug' only
  schemaLocation "demoDebug", "$projectDir/schemas/demoDebug"

  // Applies to 'demoDebug' and 'demoRelease'
  schemaLocation "demo", "$projectDir/schemas/demo"

  // Applies to 'demoDebug' and 'fullDebug'
  schemaLocation "debug", "$projectDir/schemas/debug"

  // Applies to variants that aren't matched by other configurations.
  schemaLocation "$projectDir/schemas"
}

Kotlin

room {
  // Applies to 'demoDebug' only
  schemaLocation("demoDebug", "$projectDir/schemas/demoDebug")

  // Applies to 'demoDebug' and 'demoRelease'
  schemaLocation("demo", "$projectDir/schemas/demo")

  // Applies to 'demoDebug' and 'fullDebug'
  schemaLocation("debug", "$projectDir/schemas/debug")

  // Applies to variants that aren't matched by other configurations.
  schemaLocation("$projectDir/schemas")
}

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

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

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

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

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

في إصدارات الغرفة الأقل من 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");
    }
};