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

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

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

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

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

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

في الحالات التي تتضمّن عملية نقل البيانات تغييرات معقدة في المخطط، قد لا تتمكّن أداة Room من إنشاء مسار نقل بيانات مناسب تلقائيًا. على سبيل المثال، إذا قررت хувين البيانات في جدول إلى جدولَين، لا يمكن لتطبيق 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 عنصر Maven room-testing للمساعدة في عملية الاختبار لكلّ من عمليات نقل البيانات الآلية واليدوية. لكي يعمل هذا العنصر، يجب أولاً تصدير مخطّط قاعدة بياناتك.

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

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

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

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

رائع

plugins {
  id 'androidx.room'
}

room {
  schemaDirectory "$projectDir/schemas"
}

Kotlin

plugins {
  id("androidx.room")
}

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

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

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

تُستخدَم الملفات في هذا الدليل كمدخلات ومخرجات لبعض مهام Gradle. لضمان صحة عمليات الإنشاء المتزايدة والمخزّنة مؤقتًا وتحسين أدائها، عليك استخدام ملف CommandLineArgumentProvider في Gradle لإعلام Gradle بهذا الدليل.

أولاً، انسخ فئة RoomSchemaArgProvider الموضَّحة أدناه إلىملف Gradle build الخاص بالوحدة. تُرسِل الطريقة 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 مع directory المخطّط المحدّد:

رائع

// 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 إلى تبعيات الاختبار وأضِف موقع المخطّط الذي تم تصديره كملف مجلد مواد عرض:

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() builder عند إنشاء قاعدة البيانات:

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

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

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

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