پایگاه داده اتاق خود را مهاجرت کنید

همانطور که ویژگی‌هایی را به برنامه خود اضافه و تغییر می‌دهید، باید کلاس‌های موجودیت Room و جداول پایگاه داده اصلی خود را برای انعکاس این تغییرات تغییر دهید. حفظ داده‌های کاربر که از قبل در پایگاه داده دستگاه وجود دارد، هنگامی که به‌روزرسانی برنامه، طرحواره پایگاه داده را تغییر می‌دهد، مهم است.

روم از هر دو گزینه خودکار و دستی برای مهاجرت تدریجی پشتیبانی می‌کند. مهاجرت‌های خودکار برای اکثر تغییرات اساسی طرحواره کار می‌کنند، اما ممکن است برای تغییرات پیچیده‌تر نیاز به تعریف دستی مسیرهای مهاجرت داشته باشید.

مهاجرت‌های خودکار

برای اعلام یک مهاجرت خودکار بین دو نسخه از پایگاه داده، یک حاشیه‌نویسی @AutoMigration به ویژگی autoMigrations در @Database اضافه کنید:

کاتلین

// 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() {
  ...
}

جاوا

// 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 تنظیم کنید:

کاتلین

@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
  ...
}

جاوا

@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 با بازنویسی متد Migration.migrate() به صراحت یک مسیر مهاجرت بین startVersion و endVersion تعریف می‌کند. کلاس‌های Migration خود را با استفاده از متد addMigrations() به سازنده پایگاه داده خود اضافه کنید:

کاتلین

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()

جاوا

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 صادر کند. فایل‌های JSON صادر شده، تاریخچه طرحواره پایگاه داده شما را نشان می‌دهند. این فایل‌ها را در سیستم کنترل نسخه خود ذخیره کنید تا Room بتواند نسخه‌های پایین‌تری از پایگاه داده را برای اهداف آزمایشی ایجاد کند و تولید مهاجرت خودکار را فعال کند.

تنظیم مکان طرحواره با استفاده از افزونه Room Gradle

اگر از Room نسخه ۲.۶.۰ یا بالاتر استفاده می‌کنید، می‌توانید افزونه Room Gradle را اعمال کنید و از افزونه room برای مشخص کردن دایرکتوری schema استفاده کنید.

گرووی

plugins {
  id 'androidx.room'
}

room {
  schemaDirectory "$projectDir/schemas"
}

کاتلین

plugins {
  id("androidx.room")
}

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

اگر طرحواره پایگاه داده شما بر اساس نوع، طعم یا نوع ساخت متفاوت است، باید مکان‌های مختلف را با استفاده مکرر از پیکربندی schemaDirectory() مشخص کنید، که هر کدام یک variantMatchName به عنوان اولین آرگومان دارند. هر پیکربندی می‌تواند بر اساس مقایسه ساده با نام نوع، یک یا چند نوع را مطابقت دهد.

مطمئن شوید که این موارد جامع هستند و همه متغیرها را پوشش می‌دهند. همچنین می‌توانید یک schemaDirectory() بدون variantMatchName برای مدیریت متغیرهایی که با هیچ یک از پیکربندی‌های دیگر مطابقت ندارند، اضافه کنید. به عنوان مثال، در برنامه‌ای با دو نوع ساخت 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"
}

کاتلین

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

تنظیم مکان طرحواره با استفاده از گزینه پردازنده حاشیه نویسی

اگر از نسخه ۲.۵.۲ یا پایین‌تر Room استفاده می‌کنید، یا اگر از افزونه Room Gradle استفاده نمی‌کنید، مکان schema را با استفاده از گزینه room.schemaLocation annotation processor تنظیم کنید.

فایل‌های موجود در این دایرکتوری به عنوان ورودی و خروجی برای برخی از وظایف Gradle استفاده می‌شوند. برای صحت و عملکرد buildهای افزایشی و cache شده، باید 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()]
  }
}

کاتلین

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 با دایرکتوری schema مشخص شده پیکربندی کنید:

گرووی

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

کاتلین

// 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 artifact از Room را به وابستگی‌های آزمایشی خود اضافه کنید و مکان طرحواره‌ی اکسپورت شده را به عنوان یک پوشه‌ی asset اضافه کنید:

ساخت.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.8.4"
}

کاتلین

android {
    ...
    sourceSets {
        // Adds exported schema location as test app assets.
        getByName("androidTest").assets.srcDir("$projectDir/schemas")
    }
}

dependencies {
    ...
    testImplementation("androidx.room:room-testing:2.8.4")
}

پکیج testing یک کلاس MigrationTestHelper ارائه می‌دهد که می‌تواند فایل‌های schema خروجی گرفته شده را بخواند. این پکیج همچنین رابط JUnit4 TestRule را پیاده‌سازی می‌کند، بنابراین می‌تواند پایگاه‌های داده ایجاد شده را مدیریت کند.

مثال زیر تستی را برای یک مهاجرت واحد نشان می‌دهد:

کاتلین

@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.
    }
}

جاوا

@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.
    }
}

آزمایش همه مهاجرت‌ها

اگرچه می‌توان یک مهاجرت افزایشی واحد را آزمایش کرد، اما توصیه می‌کنیم آزمایشی را در نظر بگیرید که تمام مهاجرت‌های تعریف‌شده برای پایگاه داده برنامه شما را پوشش دهد. این کار به اطمینان از عدم وجود اختلاف بین یک نمونه پایگاه داده اخیراً ایجاد شده و یک نمونه قدیمی‌تر که مسیرهای مهاجرت تعریف‌شده را دنبال کرده است، کمک می‌کند.

مثال زیر آزمایشی را برای همه مهاجرت‌های تعریف‌شده نشان می‌دهد:

کاتلین

@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()
        }
    }
}

جاوا

@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() را فراخوانی کنید:

کاتلین

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

جاوا

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

این متد به Room می‌گوید که وقتی نیاز به انجام یک مهاجرت افزایشی دارد و هیچ مسیر مهاجرت تعریف‌شده‌ای وجود ندارد، جداول موجود در پایگاه داده برنامه شما را به صورت مخرب از نو ایجاد کند.

اگر می‌خواهید Room فقط در موقعیت‌های خاص به حالت مخرب بازگردد، چند جایگزین برای fallbackToDestructiveMigration() وجود دارد:

  • اگر نسخه‌های خاصی از تاریخچه طرحواره شما باعث خطاهایی می‌شوند که نمی‌توانید با مسیرهای مهاجرت آنها را حل کنید، به جای آن fallbackToDestructiveMigrationFrom() استفاده کنید. این متد نشان می‌دهد که شما می‌خواهید Room فقط هنگام مهاجرت از نسخه‌های خاص، به حالت تخریبی بازگردد.
  • اگر می‌خواهید Room فقط هنگام مهاجرت از نسخه پایگاه داده بالاتر به نسخه پایین‌تر، به حالت تخریبی بازگردد، به جای آن fallbackToDestructiveMigrationOnDowngrade() استفاده کنید.

مدیریت مقادیر پیش‌فرض ستون‌ها هنگام ارتقا به اتاق ۲.۲.۰

در Room 2.2.0 و بالاتر، می‌توانید با استفاده از حاشیه‌نویسی @ColumnInfo(defaultValue = "...") یک مقدار پیش‌فرض برای یک ستون تعریف کنید. در نسخه‌های پایین‌تر از 2.2.0، تنها راه تعریف مقدار پیش‌فرض برای یک ستون، تعریف مستقیم آن در یک دستور SQL اجرا شده است که یک مقدار پیش‌فرض ایجاد می‌کند که Room از آن اطلاعی ندارد. این بدان معناست که اگر یک پایگاه داده در ابتدا توسط نسخه‌ای پایین‌تر از 2.2.0 از Room ایجاد شده باشد، ارتقاء برنامه شما برای استفاده از Room 2.2.0 ممکن است شما را ملزم به ارائه یک مسیر مهاجرت ویژه برای مقادیر پیش‌فرض موجود کند که بدون استفاده از APIهای Room تعریف کرده‌اید.

برای مثال، فرض کنید نسخه ۱ یک پایگاه داده، موجودیت Song را تعریف می‌کند:

کاتلین

// Song entity, database version 1, Room 2.1.0.
@Entity
data class Song(
    @PrimaryKey
    val id: Long,
    val title: String
)

جاوا

// Song entity, database version 1, Room 2.1.0.
@Entity
public class Song {
    @PrimaryKey
    final long id;
    final String title;
}

همچنین فرض کنید که نسخه ۲ از همان پایگاه داده یک ستون NOT NULL جدید اضافه می‌کند و یک مسیر مهاجرت از نسخه ۱ به نسخه ۲ تعریف می‌کند:

کاتلین

// 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 ''")
    }
}

جاوا

// 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 فقط در مسیر مهاجرت از نسخه ۱ به نسخه ۲ اعلام شده است، هر کاربری که برنامه را از نسخه ۲ نصب کند، مقدار پیش‌فرض برای tag را در طرحواره پایگاه داده خود نخواهد داشت.

در نسخه‌های پایین‌تر از ۲.۲.۰ از Room، این اختلاف بی‌ضرر است. با این حال، اگر برنامه بعداً به Room 2.2.0 یا بالاتر ارتقا یابد و کلاس موجودیت Song را طوری تغییر دهد که شامل یک مقدار پیش‌فرض برای tag با استفاده از حاشیه‌نویسی @ColumnInfo باشد، Room می‌تواند این اختلاف را ببیند. این امر منجر به عدم موفقیت در اعتبارسنجی طرحواره می‌شود.

برای اطمینان از اینکه طرحواره پایگاه داده در بین همه کاربران هنگام اعلام مقادیر پیش‌فرض ستون در مسیرهای مهاجرت قبلی شما سازگار است، اولین باری که برنامه خود را برای استفاده از Room 2.2.0 یا بالاتر ارتقا می‌دهید، موارد زیر را انجام دهید:

  1. مقادیر پیش‌فرض ستون‌ها را در کلاس‌های موجودیت مربوطه با استفاده از حاشیه‌نویسی @ColumnInfo تعریف کنید.
  2. شماره نسخه پایگاه داده را ۱ واحد افزایش دهید.
  3. یک مسیر مهاجرت به نسخه جدید تعریف کنید که استراتژی حذف و ایجاد مجدد را برای افزودن مقادیر پیش‌فرض لازم به ستون‌های موجود پیاده‌سازی کند.

مثال زیر این فرآیند را نشان می‌دهد:

کاتلین

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

جاوا

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