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

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

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

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

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

رائع

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 أو إصدارًا أقدم من الغرفة، أو لم تكن تستخدم مكوّن Gradle الإضافي للغرفة، اضبط موقع المخطط باستخدام خيار معالج التعليقات التوضيحية room.schemaLocation.

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

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 للعودة إلى الترفيه المدمر في مواقف معيّنة، هناك بعض البدائل لـ 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");
    }
};