रूम डेटाबेस को माइग्रेट करना

अपने ऐप्लिकेशन में सुविधाएं जोड़ने और उनमें बदलाव करने के बाद, आपको इन बदलावों को दिखाने के लिए, Room इकाई की क्लास और डेटाबेस टेबल में बदलाव करने होंगे. जब किसी ऐप्लिकेशन के अपडेट से डेटाबेस स्कीमा में बदलाव होता है, तो डिवाइस पर मौजूद डेटाबेस में पहले से मौजूद उपयोगकर्ता डेटा को सुरक्षित रखना ज़रूरी होता है.

Incremental migration के लिए, Room में अपने-आप और मैन्युअल, दोनों विकल्पों का इस्तेमाल किया जा सकता है. अपने-आप माइग्रेट होने की सुविधा, स्कीमा में होने वाले ज़्यादातर बुनियादी बदलावों के लिए काम करती है. हालांकि, ज़्यादा जटिल बदलावों के लिए, आपको मैन्युअल तरीके से माइग्रेशन पाथ तय करने पड़ सकते हैं.

अपने-आप होने वाले माइग्रेशन

डेटाबेस के दो वर्शन के बीच अपने-आप माइग्रेशन होने की जानकारी देने के लिए, @Database में autoMigrations प्रॉपर्टी में @AutoMigration एनोटेशन जोड़ें:

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 को वह अतिरिक्त जानकारी दी जा सकती है जो उसे माइग्रेशन पाथ सही तरीके से जनरेट करने के लिए ज़रूरी है. एक स्टैटिक क्लास तय करें, जो आपकी RoomDatabase क्लास में AutoMigrationSpec लागू करती हो. साथ ही, उस पर इनमें से एक या उससे ज़्यादा एनोटेशन लगाएं:

अपने-आप माइग्रेट होने की सुविधा के लिए, AutoMigrationSpec लागू करने का तरीका इस्तेमाल करने के लिए, उससे जुड़े @AutoMigration एनोटेशन में spec प्रॉपर्टी सेट करें:

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 क्लास, Migration.migrate() तरीका बदलकर, startVersion और endVersion के बीच माइग्रेशन पाथ के बारे में साफ़ तौर पर बताती है. addMigrations() के तरीके का इस्तेमाल करके, अपने डेटाबेस बिल्डर में Migration क्लास जोड़ें:

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, ऑटोमेटेड और मैन्युअल, दोनों तरह के माइग्रेशन की जांच करने में मदद करने के लिए, room-testing मेवन आर्टफ़ैक्ट उपलब्ध कराता है. इस आर्टफ़ैक्ट के काम करने के लिए, आपको पहले अपने डेटाबेस का स्कीमा एक्सपोर्ट करना होगा.

स्कीमा एक्सपोर्ट करना

Room, कंपाइल करने के समय आपके डेटाबेस के स्कीमा की जानकारी को JSON फ़ाइल में एक्सपोर्ट कर सकता है. एक्सपोर्ट की गई JSON फ़ाइलें, आपके डेटाबेस के स्कीमा के इतिहास को दिखाती हैं. इन फ़ाइलों को अपने वर्शन कंट्रोल सिस्टम में सेव करें, ताकि Room, जांच के मकसद से डेटाबेस के पुराने वर्शन बना सके और अपने-आप माइग्रेट होने की सुविधा चालू कर सके.

Room Gradle प्लग इन का इस्तेमाल करके, स्कीमा की जगह सेट करना

अगर Room के 2.6.0 या उसके बाद के वर्शन का इस्तेमाल किया जा रहा है, तो Room Gradle प्लग इन लागू किया जा सकता है. साथ ही, स्कीमा डायरेक्ट्री की जानकारी देने के लिए, room एक्सटेंशन का इस्तेमाल किया जा सकता है.

Groovy

plugins {
  id 'androidx.room'
}

room {
  schemaDirectory "$projectDir/schemas"
}

Kotlin

plugins {
  id("androidx.room")
}

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

अगर आपके डेटाबेस स्कीमा में वैरिएंट, फ़्लेवर या बिल्ड टाइप के आधार पर अंतर है, तो आपको schemaDirectory() कॉन्फ़िगरेशन का इस्तेमाल करके, अलग-अलग जगहों की जानकारी देनी होगी. इसके लिए, हर बार पहले आर्ग्युमेंट के तौर पर variantMatchName का इस्तेमाल करें. हर कॉन्फ़िगरेशन, वैरिएंट के नाम के आधार पर एक या उससे ज़्यादा वैरिएंट से मैच कर सकता है.

पक्का करें कि ये सभी वैरिएंट को कवर करते हों. variantMatchName के बिना schemaDirectory() को भी शामिल किया जा सकता है, ताकि ऐसे वैरिएंट को मैनेज किया जा सके जो किसी भी दूसरे कॉन्फ़िगरेशन से मैच नहीं करते. उदाहरण के लिए, ऐसे ऐप्लिकेशन में जिसमें दो तरह के 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")
}

एनोटेशन प्रोसेसर के विकल्प का इस्तेमाल करके, स्कीमा की जगह सेट करना

अगर Room के 2.5.2 या उससे पहले के वर्शन का इस्तेमाल किया जा रहा है या Room Gradle प्लग इन का इस्तेमाल नहीं किया जा रहा है, तो room.schemaLocation एनोटेशन प्रोसेसर विकल्प का इस्तेमाल करके स्कीमा की जगह सेट करें.

इस डायरेक्ट्री में मौजूद फ़ाइलों का इस्तेमाल, Gradle के कुछ टास्क के लिए इनपुट और आउटपुट के तौर पर किया जाता है. इंक्रीमेंटल और कैश मेमोरी में सेव किए गए बिल्ड की परफ़ॉर्मेंस और सटीक होने के लिए, आपको Gradle को इस डायरेक्ट्री के बारे में बताना होगा. इसके लिए, आपको Gradle के CommandLineArgumentProvider का इस्तेमाल करना होगा.

सबसे पहले, यहां दी गई 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-testingRoom से 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 क्लास होती है, जो एक्सपोर्ट की गई स्कीमा फ़ाइलों को पढ़ सकती है. पैकेज में 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() का इस्तेमाल करें.

Room 2.2.0 पर अपग्रेड करते समय, कॉलम की डिफ़ॉल्ट वैल्यू मैनेज करना

Room 2.2.0 और इसके बाद के वर्शन में, एनोटेशन @ColumnInfo(defaultValue = "...") का इस्तेमाल करके, किसी कॉलम के लिए डिफ़ॉल्ट वैल्यू तय की जा सकती है. 2.2.0 से पहले के वर्शन में, किसी कॉलम के लिए डिफ़ॉल्ट वैल्यू तय करने का एक ही तरीका है. इसके लिए, सीधे तौर पर लागू किए गए SQL स्टेटमेंट में वैल्यू तय करें. इससे एक ऐसी डिफ़ॉल्ट वैल्यू बनती है जिसके बारे में Room को नहीं पता होता. इसका मतलब है कि अगर किसी डेटाबेस को Room के 2.2.0 से पहले के वर्शन का इस्तेमाल करके बनाया गया है, तो हो सकता है कि Room 2.2.0 का इस्तेमाल करने के लिए, आपको अपने ऐप्लिकेशन को अपग्रेड करना पड़े. ऐसा करने के लिए, आपको उन मौजूदा डिफ़ॉल्ट वैल्यू के लिए, डेटा को दूसरी जगह भेजने का एक खास पाथ देना पड़ सकता है जिन्हें आपने Room API का इस्तेमाल किए बिना तय किया था.

उदाहरण के लिए, मान लें कि किसी डेटाबेस के वर्शन 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 इकाई क्लास को बदलकर, @ColumnInfo एनोटेशन का इस्तेमाल करके tag के लिए डिफ़ॉल्ट वैल्यू शामिल करता है, तो Room को यह अंतर दिख सकता है. इस वजह से, स्कीमा की पुष्टि नहीं हो पाती.

यह पक्का करने के लिए कि आपके पिछले माइग्रेशन पाथ में कॉलम की डिफ़ॉल्ट वैल्यू तय होने पर, डेटाबेस स्कीमा सभी उपयोगकर्ताओं के लिए एक जैसा हो, Room 2.2.0 या इसके बाद के वर्शन का इस्तेमाल करने के लिए, पहली बार अपने ऐप्लिकेशन को अपग्रेड करते समय ये काम करें:

  1. @ColumnInfo एनोटेशन का इस्तेमाल करके, कॉलम की डिफ़ॉल्ट वैल्यू को उनकी इकाई क्लास में बताएं.
  2. डेटाबेस के वर्शन नंबर को एक बढ़ाएं.
  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");
    }
};