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

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

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

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

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

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

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

ग्रूवी

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 हैं. ऐसे में, ये कॉन्फ़िगरेशन मान्य होंगे:

ग्रूवी

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 Plugin का इस्तेमाल नहीं किया जा रहा है, तो room.schemaLocationannotation processor विकल्प का इस्तेमाल करके स्कीमा की जगह सेट करें.

इस डायरेक्ट्री में मौजूद फ़ाइलों का इस्तेमाल, Gradle के कुछ टास्क के लिए इनपुट और आउटपुट के तौर पर किया जाता है. इंक्रीमेंटल और कैश मेमोरी में सेव किए गए बिल्ड के सही तरीके से काम करने और उनकी परफ़ॉर्मेंस के लिए, आपको Gradle के CommandLineArgumentProvider का इस्तेमाल करना होगा, ताकि 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"))
        )
      }
    }
  }
}

किसी एक माइग्रेशन को टेस्ट करना

माइग्रेशन की जांच करने से पहले, Room से 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.7.2"
}

Kotlin

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

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

टेस्टिंग पैकेज में एक 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 को सिर्फ़ कुछ वर्शन से माइग्रेट करते समय, टेबल मिटाकर फिर से बनाने के लिए कहना है.
  • अगर आपको 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 का इस्तेमाल किए बिना तय किया था.

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

अगर आपने माइग्रेशन के पुराने पाथ में कॉलम की डिफ़ॉल्ट वैल्यू तय की हैं, तो यह पक्का करने के लिए कि डेटाबेस स्कीमा सभी उपयोगकर्ताओं के लिए एक जैसा हो, 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");
    }
};