আপনার অ্যাপে ফিচার যোগ ও পরিবর্তন করার সাথে সাথে, এই পরিবর্তনগুলো প্রতিফলিত করার জন্য আপনার Room এনটিটি ক্লাস এবং অন্তর্নিহিত ডেটাবেস টেবিলগুলো সংশোধন করতে হবে। যখন কোনো অ্যাপ আপডেটের ফলে ডেটাবেস স্কিমা পরিবর্তিত হয়, তখন ডিভাইসের ডেটাবেসে আগে থেকে থাকা ব্যবহারকারীর ডেটা সংরক্ষণ করা গুরুত্বপূর্ণ।
রুম ইনক্রিমেন্টাল মাইগ্রেশনের জন্য স্বয়ংক্রিয় এবং ম্যানুয়াল উভয় বিকল্পই সমর্থন করে। বেশিরভাগ সাধারণ স্কিমা পরিবর্তনের জন্য স্বয়ংক্রিয় মাইগ্রেশন কাজ করে, কিন্তু আরও জটিল পরিবর্তনের জন্য আপনাকে ম্যানুয়ালি মাইগ্রেশন পাথ নির্ধারণ করতে হতে পারে।
স্বয়ংক্রিয় স্থানান্তর
দুটি ডাটাবেস সংস্করণের মধ্যে স্বয়ংক্রিয় মাইগ্রেশন ঘোষণা করতে, @Database এর autoMigrations প্রপার্টিতে একটি @AutoMigration অ্যানোটেশন যোগ করুন:
// Database class before the version update. @Database( version = 1, entities = [User::class] ) abstract class AppDatabaseV1 : RoomDatabase() { abstract fun userDao(): UserDao } // Database class after the version update. @Database( version = 2, entities = [User::class], autoMigrations = [ AutoMigration(from = 1, to = 2) ] ) abstract class AppDatabaseV2 : RoomDatabase() { abstract fun userDao(): UserDao }
স্বয়ংক্রিয় মাইগ্রেশন স্পেসিফিকেশন
যদি Room অস্পষ্ট স্কিমা পরিবর্তন শনাক্ত করে এবং আরও ইনপুট ছাড়া মাইগ্রেশন প্ল্যান তৈরি করতে না পারে, তাহলে এটি একটি কম্পাইল-টাইম এরর দেখায় এবং আপনাকে অবশ্যই একটি AutoMigrationSpec ইমপ্লিমেন্টেশন প্রদান করতে হবে। সাধারণত, এটি তখন ঘটে যখন কোনো মাইগ্রেশনে নিম্নলিখিতগুলির মধ্যে একটি অন্তর্ভুক্ত থাকে:
- টেবিল মুছে ফেলা বা নাম পরিবর্তন করা।
- একটি কলাম মুছে ফেলা বা তার নাম পরিবর্তন করা।
মাইগ্রেশন পাথ সঠিকভাবে তৈরি করার জন্য Room-এর প্রয়োজনীয় অতিরিক্ত তথ্য সরবরাহ করতে আপনি AutoMigrationSpec ব্যবহার করতে পারেন। আপনার RoomDatabase ক্লাসে AutoMigrationSpec ইমপ্লিমেন্ট করে এমন একটি ক্লাস সংজ্ঞায়িত করুন এবং এটিকে নিম্নলিখিত এক বা একাধিক অ্যানোটেশন দিয়ে চিহ্নিত করুন:
স্বয়ংক্রিয় মাইগ্রেশনের জন্য AutoMigrationSpec ইমপ্লিমেন্টেশন ব্যবহার করতে, সংশ্লিষ্ট @AutoMigration অ্যানোটেশনে spec প্রপার্টিটি সেট করুন:
@Database( version = 2, entities = [User::class], autoMigrations = [ AutoMigration ( from = 1, to = 2, spec = MigrationSpec1To2::class ) ] ) abstract class AppDatabaseWithSpec : RoomDatabase() { abstract fun userDao(): UserDao } @RenameTable(fromTableName = "User", toTableName = "AppUser") internal class MigrationSpec1To2 : AutoMigrationSpec
স্বয়ংক্রিয় মাইগ্রেশন সম্পন্ন হওয়ার পর যদি আপনার অ্যাপের আরও কাজ করার প্রয়োজন হয়, তাহলে আপনি onPostMigrate ইমপ্লিমেন্ট করতে পারেন। আপনি যদি আপনার AutoMigrationSpec এ এই ফাংশনটি ইমপ্লিমেন্ট করেন, তাহলে স্বয়ংক্রিয় মাইগ্রেশন সম্পন্ন হওয়ার পর Room এটিকে কল করে।
ম্যানুয়াল মাইগ্রেশন
যদি কোনো মাইগ্রেশনে জটিল স্কিমা পরিবর্তন জড়িত থাকে, তাহলে Room স্বয়ংক্রিয়ভাবে একটি উপযুক্ত মাইগ্রেশন পাথ তৈরি করতে সক্ষম নাও হতে পারে। উদাহরণস্বরূপ, যদি আপনি একটি টেবিলের ডেটা দুটি টেবিলে ভাগ করার সিদ্ধান্ত নেন, তাহলে Room এই বিভাজনটি কীভাবে করবে তা নির্ধারণ করতে পারে না। এই ধরনের পরিস্থিতিতে, আপনাকে অবশ্যই একটি Migration ক্লাস ইমপ্লিমেন্ট করার মাধ্যমে ম্যানুয়ালি একটি মাইগ্রেশন পাথ সংজ্ঞায়িত করতে হবে।
একটি Migration ক্লাস ` migrate ফাংশনটি ওভাররাইড করার মাধ্যমে ` startVersion এবং ` endVersion এর মধ্যে একটি মাইগ্রেশন পথ সুস্পষ্টভাবে নির্ধারণ করে। addMigrations ফাংশনটি ব্যবহার করে আপনার ডেটাবেস বিল্ডারে Migration ক্লাসগুলো যোগ করুন:
val MIGRATION_1_2 = object : Migration(1, 2) { override suspend fun migrate(connection: SQLiteConnection) { connection.executeSQL("CREATE TABLE `Fruit` (`id` INTEGER, `name` TEXT, " + "PRIMARY KEY(`id`))") } } val MIGRATION_2_3 = object : Migration(2, 3) { override suspend fun migrate(connection: SQLiteConnection) { connection.executeSQL("ALTER TABLE Book ADD COLUMN pub_year INTEGER") } } Room.databaseBuilder<ManualMigrationDatabase>(applicationContext, "database-name") .addMigrations(MIGRATION_1_2, MIGRATION_2_3) .build()
যখন আপনি আপনার মাইগ্রেশন পাথ নির্ধারণ করেন, তখন আপনি কিছু ভার্সনের জন্য স্বয়ংক্রিয় মাইগ্রেশন এবং অন্যগুলোর জন্য ম্যানুয়াল মাইগ্রেশন ব্যবহার করতে পারেন। যদি আপনি একই ভার্সনের জন্য একটি স্বয়ংক্রিয় মাইগ্রেশন এবং একটি ম্যানুয়াল মাইগ্রেশন উভয়ই নির্ধারণ করেন, তাহলে Room ম্যানুয়াল মাইগ্রেশনটি ব্যবহার করবে।
পরীক্ষার স্থানান্তর
মাইগ্রেশন প্রায়শই জটিল হয়, এবং ভুলভাবে সংজ্ঞায়িত মাইগ্রেশনের কারণে আপনার অ্যাপ ক্র্যাশ করতে পারে। আপনার অ্যাপের স্থিতিশীলতা বজায় রাখতে, আপনার মাইগ্রেশনগুলো পরীক্ষা করুন। স্বয়ংক্রিয় এবং ম্যানুয়াল উভয় প্রকার মাইগ্রেশনের টেস্টিং প্রক্রিয়ায় সহায়তা করার জন্য Room একটি room3-testing Maven আর্টিফ্যাক্ট প্রদান করে। এই আর্টিফ্যাক্টটি কাজ করার জন্য, আপনাকে প্রথমে আপনার ডাটাবেসের স্কিমা এক্সপোর্ট করতে হবে।
স্কিমা রপ্তানি করুন
Room কম্পাইল করার সময় আপনার ডাটাবেসের স্কিমা তথ্য একটি JSON ফাইলে এক্সপোর্ট করে। এক্সপোর্ট করা JSON ফাইলগুলো আপনার ডাটাবেসের স্কিমা ইতিহাসকে উপস্থাপন করে। এই ফাইলগুলো আপনার ভার্সন কন্ট্রোল সিস্টেমে সংরক্ষণ করুন, যাতে আপনি পরীক্ষার জন্য ডাটাবেসের পূর্ববর্তী সংস্করণগুলো পুনরায় তৈরি করতে পারেন এবং স্বয়ংক্রিয় মাইগ্রেশন জেনারেশনকে সমর্থন করতে পারেন।
Room Gradle Plugin ব্যবহার করে স্কিমা অবস্থান নির্ধারণ করুন
স্কিমা ডিরেক্টরি নির্দিষ্ট করতে, Room Gradle Plugin প্রয়োগ করুন এবং room3 এক্সটেনশনটি ব্যবহার করুন।
গ্রুভি
plugins {
id 'androidx.room3'
}
room3 {
schemaDirectory "$projectDir/schemas"
}
কোটলিন
plugins {
id("androidx.room3")
}
room3 {
schemaDirectory("$projectDir/schemas")
}
যদি আপনার ডাটাবেস স্কিমা ভ্যারিয়েন্ট, ফ্লেভার বা বিল্ড টাইপের উপর ভিত্তি করে ভিন্ন হয়, তবে আপনাকে অবশ্যই schemaDirectory কনফিগারেশনটি একাধিকবার ব্যবহার করে ভিন্ন ভিন্ন অবস্থান নির্দিষ্ট করতে হবে, যেখানে প্রতিটির প্রথম আর্গুমেন্ট হিসেবে একটি variantMatchName থাকবে। প্রতিটি কনফিগারেশন ভ্যারিয়েন্টের নামের সাথে সাধারণ তুলনার ভিত্তিতে এক বা একাধিক ভ্যারিয়েন্টকে মেলাতে পারে।
নিশ্চিত করুন যে এগুলো সম্পূর্ণ এবং সমস্ত ভ্যারিয়েন্টকে অন্তর্ভুক্ত করে। অন্য কোনো কনফিগারেশনের সাথে মেলে না এমন ভ্যারিয়েন্টগুলো পরিচালনা করার জন্য আপনি variantMatchName ছাড়াই একটি schemaDirectory() অন্তর্ভুক্ত করতে পারেন। উদাহরণস্বরূপ, demo এবং full দুটি বিল্ড ফ্লেভার এবং debug এবং release দুটি বিল্ড টাইপ সহ একটি অ্যাপে, নিম্নলিখিতগুলো বৈধ কনফিগারেশন:
গ্রুভি
room3 {
// 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"
}
কোটলিন
room3 {
// 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 Gradle প্লাগইন ব্যবহার না করেন, তাহলে room.schemaLocation অ্যানোটেশন প্রসেসর অপশনটি ব্যবহার করে স্কিমা লোকেশন সেট করুন।
Gradle কিছু টাস্কের জন্য এই ডিরেক্টরির ফাইলগুলোকে ইনপুট এবং আউটপুট হিসেবে ব্যবহার করে। ইনক্রিমেন্টাল এবং ক্যাশড বিল্ডের সঠিকতা ও পারফরম্যান্সের জন্য, আপনাকে অবশ্যই Gradle-এর CommandLineArgumentProvider ব্যবহার করে এই ডিরেক্টরিটি সম্পর্কে Gradle-কে জানাতে হবে।
প্রথমে, নিম্নলিখিত RoomSchemaArgProvider ক্লাসটি আপনার মডিউলের Gradle বিল্ড ফাইলে কপি করুন। নমুনা ক্লাসের asArguments ফাংশনটি KSP তে room.schemaLocation=${schemaDir.path} পাস করে। আপনি যদি 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() {
return ["room.schemaLocation=${schemaDir.path}".toString()]
}
}
কোটলিন
class RoomSchemaArgProvider(
@get:InputDirectory
@get:PathSensitive(PathSensitivity.RELATIVE)
val schemaDir: File
) : CommandLineArgumentProvider {
override fun asArguments(): Iterable<String> {
return listOf("room.schemaLocation=${schemaDir.path}")
}
}
এরপর, নির্দিষ্ট স্কিমা ডিরেক্টরির সাথে RoomSchemaArgProvider ব্যবহার করার জন্য কম্পাইল অপশনগুলো কনফিগার করুন:
গ্রুভি
ksp {
arg(new RoomSchemaArgProvider(new File(projectDir, "schemas")))
}
কোটলিন
ksp {
arg(RoomSchemaArgProvider(File(projectDir, "schemas")))
}
একটি একক মাইগ্রেশন পরীক্ষা করুন
আপনার মাইগ্রেশনগুলো পরীক্ষা করার আগে, androidx.room3:room3-testing আর্টিফ্যাক্টটি আপনার টেস্ট ডিপেন্ডেন্সিতে যোগ করুন এবং এক্সপোর্ট করা স্কিমার অবস্থানটিকে একটি অ্যাসেট ডিরেক্টরি হিসেবে যুক্ত করুন:
গ্রুভি
android { ... sourceSets { // Adds exported schema location as test app assets if not using // the Room Gradle Plugin. androidTest.assets.srcDirs += files("$projectDir/schemas".toString()) } } dependencies { ... androidTestImplementation "androidx.room3:room3-testing:3.0.1" }
কোটলিন
android { ... sourceSets { // Adds exported schema location as test app assets if not using // the Room Gradle Plugin. getByName("androidTest").assets.srcDir("$projectDir/schemas") } } dependencies { ... testImplementation("androidx.room3:room3-testing:3.0.1") }
টেস্টিং প্যাকেজটিতে একটি MigrationTestHelper ক্লাস রয়েছে, যা এক্সপোর্ট করা স্কিমা ফাইলগুলো পড়তে পারে। এছাড়াও, প্যাকেজটি তৈরি করা ডাটাবেসগুলো পরিচালনা করার জন্য JUnit4 TestRule ইন্টারফেসটি ইমপ্লিমেন্ট করে।
নিম্নলিখিত উদাহরণটি একটি একক মাইগ্রেশনের জন্য একটি পরীক্ষা প্রদর্শন করে:
@RunWith(AndroidJUnit4::class) class MigrationTest { private val TEST_DB = "migration-test" private val instrumentation = InstrumentationRegistry.getInstrumentation() @get:Rule val helper = MigrationTestHelper( instrumentation = instrumentation, databaseClass = MigrationDb::class, driver = AndroidSQLiteDriver(), file = instrumentation.targetContext.getDatabasePath(TEST_DB), ) @Test fun migrate1To2() = runTest { val connection = helper.createDatabase(1) // Database has schema version 1. Insert some data using SQL queries. // You can't use DAO classes because they expect the latest schema. connection.execSQL("INSERT INTO User (id, name) VALUES (1, 'John Doe')") connection.close() // Re-open the database with version 2 and provide MIGRATION_1_2 val migratedConnection = helper.runMigrationsAndValidate(2, listOf(MIGRATION_1_2)) // MigrationTestHelper automatically verifies the schema changes, // but you need to validate that the data was migrated properly. val hasData = migratedConnection.prepare("SELECT COUNT(*) FROM User").use { it.step() it.getLong(0) > 0 } assertTrue("Expected data was not migrated", hasData) migratedConnection.close() } }
সমস্ত মাইগ্রেশন পরীক্ষা করুন
যদিও আপনি একটিমাত্র ইনক্রিমেন্টাল মাইগ্রেশন পরীক্ষা করতে পারেন, আপনার অ্যাপের ডাটাবেসের জন্য সংজ্ঞায়িত সমস্ত মাইগ্রেশনকে অন্তর্ভুক্ত করে এমন একটি পরীক্ষাও করা উচিত। এটি নিশ্চিত করতে সাহায্য করে যে, সম্প্রতি তৈরি করা একটি ডাটাবেস ইনস্ট্যান্স এবং সংজ্ঞায়িত মাইগ্রেশন পথ অনুসরণকারী পূর্ববর্তী কোনো ইনস্ট্যান্সের মধ্যে কোনো অমিল নেই।
নিম্নলিখিত উদাহরণটি সমস্ত সংজ্ঞায়িত মাইগ্রেশনের জন্য একটি পরীক্ষা প্রদর্শন করে:
@RunWith(AndroidJUnit4::class) class MigrationTest { private val TEST_DB = "migration-test" private val instrumentation = InstrumentationRegistry.getInstrumentation() // Array of all migrations. private val ALL_MIGRATIONS = arrayOf(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4) @get:Rule val helper: MigrationTestHelper = MigrationTestHelper( instrumentation = instrumentation, databaseClass = MigrationDb::class, driver = AndroidSQLiteDriver(), file = instrumentation.targetContext.getDatabasePath(TEST_DB), ) @Test fun migrateAll() = runTest { // Create earliest version of the database. val connection = helper.createDatabase(1) connection.close() // Create latest version of the database. val db = Room.databaseBuilder<AppDatabase>(instrumentation.targetContext, TEST_DB) .setDriver(AndroidSQLiteDriver()) .addMigrations(*ALL_MIGRATIONS) .build() // Open the database, Room validates the schema once all migrations // execute. db.useReaderConnection { connection -> // Perform additional validation } db.close() } }
অনুপস্থিত মাইগ্রেশন পাথগুলি সুন্দরভাবে পরিচালনা করুন
যদি Room কোনো ডিভাইসে থাকা ডেটাবেসকে বর্তমান সংস্করণে আপগ্রেড করার জন্য কোনো মাইগ্রেশন পাথ খুঁজে না পায়, তাহলে একটি IllegalStateException ঘটে। যদি মাইগ্রেশন পাথ না থাকার কারণে বিদ্যমান ডেটা হারিয়ে যাওয়াটা গ্রহণযোগ্য হয়, তাহলে ডেটাবেস তৈরি করার সময় fallbackToDestructiveMigration বিল্ডার ফাংশনটি কল করুন:
Room.databaseBuilder<FallbackMigrationDatabase>(applicationContext, "database-name") .fallbackToDestructiveMigration() .build()
এই ফাংশনটি Room-কে এমনভাবে কনফিগার করে, যাতে ইনক্রিমেন্টাল মাইগ্রেশন করার প্রয়োজন হলে এবং কোনো নির্ধারিত মাইগ্রেশন পাথ না থাকলে, এটি আপনার অ্যাপের ডেটাবেসের টেবিলগুলোকে ডেস্ট্রাকটিভ পদ্ধতিতে পুনরায় তৈরি করে।
শুধুমাত্র নির্দিষ্ট পরিস্থিতিতে ধ্বংসাত্মক বিনোদনের আশ্রয় নিতে, fallbackToDestructiveMigration এর নিম্নলিখিত বিকল্পগুলির মধ্যে একটি ব্যবহার করুন:
- আপনার স্কিমা হিস্টোরির নির্দিষ্ট সংস্করণগুলির কারণে যদি এমন ত্রুটি দেখা দেয় যা আপনি মাইগ্রেশন পাথ ব্যবহার করে সমাধান করতে পারছেন না, তাহলে তার পরিবর্তে
fallbackToDestructiveMigrationFromব্যবহার করুন। এই ফাংশনটি নির্দেশ করে যে, আপনি চান Room শুধুমাত্র নির্দিষ্ট সংস্করণ থেকে মাইগ্রেট করার সময়ই ডেস্ট্রাকটিভ রিক্রিয়েশন পদ্ধতিতে ফিরে যাক। - আপনি যদি চান যে Room শুধুমাত্র উচ্চতর ডাটাবেস সংস্করণ থেকে নিম্নতর সংস্করণে স্থানান্তরের সময়ই ধ্বংসাত্মক পুনর্গঠন পদ্ধতিতে ফিরে যাক, তাহলে এর পরিবর্তে
fallbackToDestructiveMigrationOnDowngradeব্যবহার করুন।