[Jetpack] Use Migration in Room to upgrade database exception handling (Migration of multiple database versions | fallbackToDestructiveMigration() function handles upgrade exceptions)

Article directory

  • 1. Room#Migration migration tool upgrades the database
  • 2. Migration of multiple database versions
  • 3. Database exception handling – RoomDatabase.Builder#fallbackToDestructiveMigration() function
  • 4. Complete code example

1. Room#Migration migration tool upgrades the database

Room Migration Database Migration Tool is part of Android Jetpack Architecture Components (Architecture Components) , it is a handy database migration tool, used to provide automated migration solution for database created using Room framework in Android; font>

Room Migration database migration tool is used as follows:

  • Database modification: Modify database table structure;
  • Migration code: Write migration code for each database version;
  • Automatic update: Automatically detect the database version number and automatically perform data migration when executing the application;

Save database data before migration: When changing the schema in the Room database in the application, will need to perform a database migration to preserve the old data and prevent the application from crashing;< /font>

Automatically run: Room Migration database migration tool will automatically create migration files and apply them to the database, to keep the SQLite database with the latest schema ;

2. Migration of multiple database versions

In the original version 1 database, has the following: id, name, age, three fields;

@Entity(tableName = "student")
class Student {<!-- -->
    /**
     * @PrimaryKey set the primary key autoGenerate to self-increment
     * @ColumnInfo name set column name / typeAffinity set column type
     */
    @PrimaryKey(autoGenerate = true)
    @ColumnInfo(name = "id", typeAffinity = ColumnInfo. INTEGER)
    var id: Int = 0

    /**
     * name field
     * The column name in the database table is name
     * The type in the database table is TEXT text type
     */
    @ColumnInfo(name = "name", typeAffinity = ColumnInfo. TEXT)
    lateinit var name: String

    /**
     * age field
     * The column name in the database table is age
     * The type in the database table is INTEGER text type
     */
    @ColumnInfo(name = "age", typeAffinity = ColumnInfo.INTEGER)
    var age: Int = 0
}

Upgraded from database version 1 to database version 2, Added sex field;

 /**
         * Database version 1 upgrade to version 2 migration class instance object
         */
        val MIGRATION_1_2: Migration = object : Migration(1, 2) {<!-- -->
            override fun migrate(database: SupportSQLiteDatabase) {<!-- -->
                Log.i("Room_StudentDatabase", "Upgrade database version 1 to version 2")
                database.execSQL("alter table student add column sex integer not null default 1")
            }
        }

Upgrade from database version 2 to database version 3, add degree field;

 /**
         * Database version 2 upgrade to version 3 migration class instance object
         */
        val MIGRATION_2_3: Migration = object : Migration(2, 3) {<!-- -->
            override fun migrate(database: SupportSQLiteDatabase) {<!-- -->
                Log.i("Room_StudentDatabase", "Database version 2 upgraded to version 3")
                database.execSQL("alter table student add column degree integer not null default 1")
            }
        }

The user has run the database before, It is possible to install any version of the database version 1 / version 2 / version 3;

Database Version 1 -> Database Version 3 Upgrade Procedure:

If the user was running database version 1 before, then when running the latest application, first execute

val MIGRATION_1_2: Migration = object : Migration(1, 2)

The migration operation corresponding to the migration object, first upgrade from database version 1 to database version 2;

and then execute

val MIGRATION_2_3: Migration = object : Migration(2, 3)

The migration operation corresponding to the migration object, upgrade from database version 2 to database version 3;

Database Version 2 -> Database Version 3 Upgrade Procedure:

If the previous version of the database in the user’s mobile phone is version 2, then when running the latest application, directly execute

val MIGRATION_2_3: Migration = object : Migration(2, 3)

The migration operation corresponding to the migration object, upgrade from database version 2 to database version 3;

3. Database exception handling – RoomDatabase.Builder#fallbackToDestructiveMigration() function

In the previous blog [Jetpack] use Migration in Room to upgrade the database (modify Entity entity class – change data model | create Migration migration class | modify database version | code example), explained how to use Migration to upgrade the database;

First, create the Migration migration class,

 companion object {<!-- -->
        /**
         * Database version 1 upgrade to version 2 migration class instance object
         */
        val MIGRATION_1_2: Migration = object : Migration(1, 2) {<!-- -->
            override fun migrate(database: SupportSQLiteDatabase) {<!-- -->
                Log.i("StudentDatabase", "Database version 1 upgraded to version 2")
                database.execSQL("alter table student add column sex integer not null default 1")
            }
        }
}

Then, modify the database version;

@Database(entities = [Student::class], version = 1, exportSchema = false)
abstract class StudentDatabase: RoomDatabase() {<!-- -->

If only modified the database version on the @Database annotation of RoomDatabase, did not create a corresponding Migration migration class, Then an IllegalStateException will appear;

The error message is as follows:

2023-06-05 10:47:13.635 E/AndroidRuntime: FATAL EXCEPTION: arch_disk_io_0
    Process: kim.hsl.rvl, PID: 31463
    java.lang.RuntimeException: Exception while computing database live data.
        at androidx.room.RoomTrackingLiveData$1.run(RoomTrackingLiveData.java:92)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
        at java.lang.Thread.run(Thread.java:930)
     Caused by: java.lang.IllegalStateException: A migration from 2 to 4 was required but not found. Please provide the necessary Migration path via RoomDatabase.Builder.addMigration(Migration ...) or allow for destructive migrations via one of the RoomDatabase. Builder.fallbackToDestructiveMigration* methods.
        at androidx.room.RoomOpenHelper.onUpgrade(RoomOpenHelper.java:117)
        at androidx.sqlite.db.framework.FrameworkSQLiteOpenHelper$OpenHelper.onUpgrade(FrameworkSQLiteOpenHelper.java:124)
        at android.database.sqlite.SQLiteOpenHelper.getDatabaseLocked(SQLiteOpenHelper.java:435)
        at android.database.sqlite.SQLiteOpenHelper.getWritableDatabase(SQLiteOpenHelper.java:331)
        at androidx.sqlite.db.framework.FrameworkSQLiteOpenHelper$OpenHelper.getWritableSupportDatabase(FrameworkSQLiteOpenHelper.java:92)
        at androidx.sqlite.db.framework.FrameworkSQLiteOpenHelper.getWritableDatabase(FrameworkSQLiteOpenHelper.java:53)
        at androidx.room.RoomDatabase.inTransaction(RoomDatabase.java:476)
        at androidx.room.RoomDatabase.assertNotSuspendingTransaction(RoomDatabase.java:281)
        at androidx.room.RoomDatabase.query(RoomDatabase.java:324)
        at androidx.room.util.DBUtil.query(DBUtil.java:83)
        at kim.hsl.rvl.StudentDao_Impl$4.call(StudentDao_Impl.java:125)
        at kim.hsl.rvl.StudentDao_Impl$4.call(StudentDao_Impl.java:122)
        at androidx.room.RoomTrackingLiveData$1.run(RoomTrackingLiveData.java:90)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
        at java.lang.Thread.run(Thread.java:930)
    
    
    ---------beginning of system

To handle the above exceptions, execute the RoomDatabase.Builder#fallbackToDestructiveMigration() function when creates RoomDatabase.Builder, and then use Migration after When migrating the database, if there is an exception, will rebuild the database table, but the previous database data will also be emptied accordingly;< /font>

 // create database
                    instance = Room.databaseBuilder(
                        context. applicationContext,
                        StudentDatabase::class.java,
                        "student_database.db")
                        .addMigrations(MIGRATION_1_2)
                        .addMigrations(MIGRATION_2_3)
                        .fallbackToDestructiveMigration()
                        .allowMainThreadQueries() // Room does not allow database operations on the main thread in principle
                                                  // If you want to operate the database on the main thread, you need to call this function
                        .build()

When running again, the printed log results are as follows:

2023-06-05 10:52:41.757 I/Room_MainActivity: Observer#onChanged callback, List<Student>: []
2023-06-05 10:52:42.154 I/Room_MainActivity: Insert data S1 : Student(id=0, name='Tom', age=18)
2023-06-05 10:52:42.158 I/Room_MainActivity: Observer#onChanged callback, List<Student>: [Student(id=1, name='Tom', age=18)]
2023-06-05 10:52:42.664 I/Room_MainActivity: Insert data S2 : Student(id=0, name='Jerry', age=16)
2023-06-05 10:52:42.666 I/Room_MainActivity: Observer#onChanged callback, List<Student>: [Student(id=1, name='Tom', age=18), Student(id=2, name= 'Jerry', age=16)]
2023-06-05 10:52:43.174 I/Room_MainActivity: Update data S2 : Student(id=2, name='Jack', age=60)
2023-06-05 10:52:43.176 I/Room_MainActivity: Observer#onChanged callback, List<Student>: [Student(id=1, name='Tom', age=18), Student(id=2, name= 'Jack', age=60)]
2023-06-05 10:52:43.681 I/Room_MainActivity: Delete data id = 1
2023-06-05 10:52:43.683 I/Room_MainActivity: Observer#onChanged callback, List<Student>: [Student(id=2, name='Jack', age=60)]
2023-06-05 10:52:44.182 I/Room_MainActivity: active query: LiveData: androidx.room.RoomTrackingLiveData@b957950, actual data: null
2023-06-05 10:52:44.183 I/Room_MainActivity: Active query 2: [Student(id=2, name='Jack', age=60)]

The log printed in the first line is Observer#onChanged callback, List: [] , the current database is empty, and the previous data is cleared, and the logs printed at this time are all for this application newly inserted data at runtime;

4. Complete code example

Code address: https://github.com/han1202012/Room_ViewModel_LiveData

syntaxbug.com © 2021 All Rights Reserved.