【问题标题】:Android Room migrate from SQLiteOpenHelperAndroid Room 从 SQLiteOpenHelper 迁移
【发布时间】:2021-06-29 17:11:43
【问题描述】:

我正在将数据库访问从 SQLiteOpenHelper 迁移到 Room。
但是我注意到 Room 不接受数据库架构。
有一个表的主键由两列组成,其中一列可以为空。 在 Room 中,主键不能为空。

所以我想在开始使用 Room 之前执行查询以修复架构。

使用 SQLiteOpenHelper 设置的当前数据库版本为 8,我将 Room 的数据库版本设置为 9。

我在 Room 中添加了迁移,因此可以执行升级但没有任何反应。

Room.databaseBuilder(
    context.applicationContext,
    AppDatabase::class.java,
    "databse")
    .addMigrations(MIGRATION_8_9)
    .fallbackToDestructiveMigration()
    .build()



private val MIGRATION_8_9 = object: Migration(8, 9) {
    override fun migrate(database: SupportSQLiteDatabase) {
        L.tag(TAG).info("Performing database migration from SQLiteHelper to Room")
        database.execSQL("DO SOME WORK")
    }

}

如何在开始使用 Room 之前运行 SQLite 语句来修复数据库架构?

【问题讨论】:

    标签: android android-sqlite android-room


    【解决方案1】:

    我在 Room 中添加了迁移,因此可以执行升级但没有任何反应。

    您的代码应该可以工作(根据下面的演示)但是只有当您实际尝试对数据库执行某些操作而不是实例化它时。即数据库在实际需要时才打开。

    考虑以下示例:-

    @Database(entities = [MyTable::class],version = 9,exportSchema = false)
    abstract class TheDatabase: RoomDatabase() {
        abstract fun getAllDao(): AllDao
    
        companion object {
            private var instance: TheDatabase? = null
            private val TAG = "ROOMDBINFO"
    
            fun getInstance(context: Context): TheDatabase {
                if (instance == null) {
                    instance = Room.databaseBuilder(context,TheDatabase::class.java,"database")
                        .allowMainThreadQueries()
                        .addMigrations(MIGRATION_8_9)
                        .build()
    
                }
                return instance as TheDatabase
            }
            private val MIGRATION_8_9 = object: Migration(8, 9) {
                override fun migrate(database: SupportSQLiteDatabase) {
                    Log.d(TAG,"Performing database migration from SQLiteHelper to Room")
                    var csr = database.query("SELECT * FROM sqlite_master")
                    DatabaseUtils.dumpCursor(csr)
                }
            }
        }
    }
    

    还有:-

    class MainActivity : AppCompatActivity() {
    
        lateinit var db: TheDatabase
        lateinit var dao: AllDao
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
            
            createBaseDatabaseToMigrate() //<<<<< Create and populate the database before Room
            
            db = TheDatabase.getInstance(this)
            dao = db.getAllDao()
            //dao.getAllFromMytable() //<<<<< Commented out so DB isn't opened
    
        }
    
    
        /* This will create the database if it doesn't exist*/
        private fun createBaseDatabaseToMigrate() {
            val TAG = "ORIGINALDATA"
            var db = openOrCreateDatabase(this.getDatabasePath("database").absolutePath,0,null)
            db.beginTransaction()
            db.execSQL("CREATE TABLE IF NOT EXISTS mytable (col1 INTEGER NOT NULL, col2 INTEGER, col3 TEXT, PRIMARY KEY(col1,col2))")
            var csr = db.query("mytable",null,null,null,null,null,null)
            var dataExists = csr.count > 0
            csr.close()
            if (!dataExists) {
                db.execSQL("INSERT OR IGNORE INTO mytable VALUES(1,null,'data1'),(2,2,'data2'),(3,3,'data3');")
                db.execSQL("PRAGMA user_version = 8;")
            } else {
                Log.d(TAG,"Data already existed.")
            }
            csr = db.query("mytable",null,null,null,null,null,null)
            while(csr.moveToNext()) {
                Log.d(TAG,
                    "COL1 = ${csr.getLong(csr.getColumnIndex("col1"))} " +
                            "COL2 = ${csr.getLong(csr.getColumnIndex("col2"))} " +
                            "COL3 = ${csr.getString(csr.getColumnIndex("col3"))}"
                )
            }
            csr = db.query("sqlite_master",null,null,null,null,null,null)
            DatabaseUtils.dumpCursor(csr)
            csr.close()
            db.setTransactionSuccessful()
            db.endTransaction()
            db.close()
        }
    }
    
    • 注意 createDatabaseToMigrate 展示了在我开始使用 Room 之前如何运行 SQLite 语句来修复数据库架构 。但是,我们不建议/不需要这样做。

    • 为了方便和简洁,在主线程上运行。

    • 注意 dao.getAllFromMytable() 已被注释掉。

    测试/演示

    使用上面的代码运行它,根据日志,迁移中没有任何反应:-

    2021-06-30 06:47:18.341 W/onversion8_to_: Accessing hidden method Landroid/view/ViewGroup;->makeOptionalFitsSystemWindows()V (light greylist, reflection)
    2021-06-30 06:47:18.407 D/ORIGINALDATA: Data already existed.
    2021-06-30 06:47:18.408 D/ORIGINALDATA: COL1 = 1 COL2 = 0 COL3 = 0
    2021-06-30 06:47:18.408 D/ORIGINALDATA: COL1 = 2 COL2 = 2 COL3 = 0
    2021-06-30 06:47:18.408 D/ORIGINALDATA: COL1 = 3 COL2 = 3 COL3 = 0
    2021-06-30 06:47:18.408 I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@be55dc1
    2021-06-30 06:47:18.408 I/System.out: 0 {
    2021-06-30 06:47:18.409 I/System.out:    type=table
    2021-06-30 06:47:18.409 I/System.out:    name=android_metadata
    2021-06-30 06:47:18.409 I/System.out:    tbl_name=android_metadata
    2021-06-30 06:47:18.409 I/System.out:    rootpage=3
    2021-06-30 06:47:18.409 I/System.out:    sql=CREATE TABLE android_metadata (locale TEXT)
    2021-06-30 06:47:18.409 I/System.out: }
    2021-06-30 06:47:18.409 I/System.out: 1 {
    2021-06-30 06:47:18.409 I/System.out:    type=table
    2021-06-30 06:47:18.409 I/System.out:    name=mytable
    2021-06-30 06:47:18.409 I/System.out:    tbl_name=mytable
    2021-06-30 06:47:18.409 I/System.out:    rootpage=4
    2021-06-30 06:47:18.409 I/System.out:    sql=CREATE TABLE mytable (col1 INTEGER NOT NULL, col2 INTEGER, col3 TEXT, PRIMARY KEY(col1,col2))
    2021-06-30 06:47:18.409 I/System.out: }
    2021-06-30 06:47:18.409 I/System.out: 2 {
    2021-06-30 06:47:18.409 I/System.out:    type=index
    2021-06-30 06:47:18.410 I/System.out:    name=sqlite_autoindex_mytable_1
    2021-06-30 06:47:18.410 I/System.out:    tbl_name=mytable
    2021-06-30 06:47:18.410 I/System.out:    rootpage=5
    2021-06-30 06:47:18.410 I/System.out:    sql=null
    2021-06-30 06:47:18.410 I/System.out: }
    2021-06-30 06:47:18.410 I/System.out: <<<<<
    2021-06-30 06:47:18.439 D/OpenGLRenderer: Skia GL Pipeline
    2021-06-30 06:47:18.460 W/onversion8_to_: Accessing hidden method Landroid/graphics/Insets;->of(IIII)Landroid/graphics/Insets; (light greylist, linking)
    

    作为第二次运行,//dao.getAllFromMytable() //&lt;&lt;&lt;&lt;&lt; Commented out so DB isn't 更改为 dao.getAllFromMytable() //&lt;&lt;&lt;&lt;&lt; Commented out so DB isn't opened

    :-

    2021-06-30 06:51:28.059 W/onversion8_to_: Accessing hidden method Landroid/view/ViewGroup;->makeOptionalFitsSystemWindows()V (light greylist, reflection)
    2021-06-30 06:51:28.129 D/ORIGINALDATA: Data already existed.
    2021-06-30 06:51:28.129 D/ORIGINALDATA: COL1 = 1 COL2 = 0 COL3 = 0
    2021-06-30 06:51:28.129 D/ORIGINALDATA: COL1 = 2 COL2 = 2 COL3 = 0
    2021-06-30 06:51:28.130 D/ORIGINALDATA: COL1 = 3 COL2 = 3 COL3 = 0
    2021-06-30 06:51:28.130 I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@be55dc1
    2021-06-30 06:51:28.130 I/System.out: 0 {
    2021-06-30 06:51:28.130 I/System.out:    type=table
    2021-06-30 06:51:28.131 I/System.out:    name=android_metadata
    2021-06-30 06:51:28.131 I/System.out:    tbl_name=android_metadata
    2021-06-30 06:51:28.131 I/System.out:    rootpage=3
    2021-06-30 06:51:28.131 I/System.out:    sql=CREATE TABLE android_metadata (locale TEXT)
    2021-06-30 06:51:28.131 I/System.out: }
    2021-06-30 06:51:28.131 I/System.out: 1 {
    2021-06-30 06:51:28.131 I/System.out:    type=table
    2021-06-30 06:51:28.131 I/System.out:    name=mytable
    2021-06-30 06:51:28.131 I/System.out:    tbl_name=mytable
    2021-06-30 06:51:28.131 I/System.out:    rootpage=4
    2021-06-30 06:51:28.131 I/System.out:    sql=CREATE TABLE mytable (col1 INTEGER NOT NULL, col2 INTEGER, col3 TEXT, PRIMARY KEY(col1,col2))
    2021-06-30 06:51:28.131 I/System.out: }
    2021-06-30 06:51:28.131 I/System.out: 2 {
    2021-06-30 06:51:28.131 I/System.out:    type=index
    2021-06-30 06:51:28.132 I/System.out:    name=sqlite_autoindex_mytable_1
    2021-06-30 06:51:28.132 I/System.out:    tbl_name=mytable
    2021-06-30 06:51:28.132 I/System.out:    rootpage=5
    2021-06-30 06:51:28.132 I/System.out:    sql=null
    2021-06-30 06:51:28.132 I/System.out: }
    2021-06-30 06:51:28.133 I/System.out: <<<<<
    
    
    
    
    2021-06-30 06:51:28.161 D/ROOMDBINFO: Performing database migration from SQLiteHelper to Room
    2021-06-30 06:51:28.162 I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@71135f2
    2021-06-30 06:51:28.162 I/System.out: 0 {
    2021-06-30 06:51:28.162 I/System.out:    type=table
    2021-06-30 06:51:28.162 I/System.out:    name=android_metadata
    2021-06-30 06:51:28.162 I/System.out:    tbl_name=android_metadata
    2021-06-30 06:51:28.162 I/System.out:    rootpage=3
    2021-06-30 06:51:28.162 I/System.out:    sql=CREATE TABLE android_metadata (locale TEXT)
    2021-06-30 06:51:28.162 I/System.out: }
    2021-06-30 06:51:28.162 I/System.out: 1 {
    2021-06-30 06:51:28.163 I/System.out:    type=table
    2021-06-30 06:51:28.163 I/System.out:    name=mytable
    2021-06-30 06:51:28.163 I/System.out:    tbl_name=mytable
    2021-06-30 06:51:28.163 I/System.out:    rootpage=4
    2021-06-30 06:51:28.163 I/System.out:    sql=CREATE TABLE mytable (col1 INTEGER NOT NULL, col2 INTEGER, col3 TEXT, PRIMARY KEY(col1,col2))
    2021-06-30 06:51:28.163 I/System.out: }
    2021-06-30 06:51:28.163 I/System.out: 2 {
    2021-06-30 06:51:28.163 I/System.out:    type=index
    2021-06-30 06:51:28.163 I/System.out:    name=sqlite_autoindex_mytable_1
    2021-06-30 06:51:28.163 I/System.out:    tbl_name=mytable
    2021-06-30 06:51:28.163 I/System.out:    rootpage=5
    2021-06-30 06:51:28.164 I/System.out:    sql=null
    2021-06-30 06:51:28.164 I/System.out: }
    2021-06-30 06:51:28.164 I/System.out: <<<<<
    
    
    
    
    2021-06-30 06:51:28.169 D/AndroidRuntime: Shutting down VM
    2021-06-30 06:51:28.171 E/AndroidRuntime: FATAL EXCEPTION: main
        Process: a.a.so68183015kotlinroommigrationconversion8_to_9, PID: 24101
        java.lang.RuntimeException: Unable to start activity ComponentInfo{a.a.so68183015kotlinroommigrationconversion8_to_9/a.a.so68183015kotlinroommigrationconversion8_to_9.MainActivity}: java.lang.IllegalStateException: Migration didn't properly handle: mytable(a.a.so68183015kotlinroommigrationconversion8_to_9.MyTable).
         Expected:
        TableInfo{name='mytable', columns={col2=Column{name='col2', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=2, defaultValue='null'}, col3=Column{name='col3', type='TEXT', affinity='2', notNull=true, primaryKeyPosition=0, defaultValue='null'}, col1=Column{name='col1', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=1, defaultValue='null'}}, foreignKeys=[], indices=[]}
         Found:
        TableInfo{name='mytable', columns={col2=Column{name='col2', type='INTEGER', affinity='3', notNull=false, primaryKeyPosition=2, defaultValue='null'}, col3=Column{name='col3', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, col1=Column{name='col1', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=1, defaultValue='null'}}, foreignKeys=[], indices=[]}
            at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2913)
            at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3048)
            at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)
            at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
            at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
            at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1808)
            at android.os.Handler.dispatchMessage(Handler.java:106)
            at android.os.Looper.loop(Looper.java:193)
            at android.app.ActivityThread.main(ActivityThread.java:6669)
            at java.lang.reflect.Method.invoke(Native Method)
            at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
         Caused by: java.lang.IllegalStateException: Migration didn't properly handle: mytable(a.a.so68183015kotlinroommigrationconversion8_to_9.MyTable).
         Expected:
        TableInfo{name='mytable', columns={col2=Column{name='col2', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=2, defaultValue='null'}, col3=Column{name='col3', type='TEXT', affinity='2', notNull=true, primaryKeyPosition=0, defaultValue='null'}, col1=Column{name='col1', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=1, defaultValue='null'}}, foreignKeys=[], indices=[]}
         Found:
        TableInfo{name='mytable', columns={col2=Column{name='col2', type='INTEGER', affinity='3', notNull=false, primaryKeyPosition=2, defaultValue='null'}, col3=Column{name='col3', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, col1=Column{name='col1', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=1, defaultValue='null'}}, foreignKeys=[], indices=[]}
            at androidx.room.RoomOpenHelper.onUpgrade(RoomOpenHelper.java:103)
            at androidx.sqlite.db.framework.FrameworkSQLiteOpenHelper$OpenHelper.onUpgrade(FrameworkSQLiteOpenHelper.java:183)
            at android.database.sqlite.SQLiteOpenHelper.getDatabaseLocked(SQLiteOpenHelper.java:398)
            at android.database.sqlite.SQLiteOpenHelper.getWritableDatabase(SQLiteOpenHelper.java:298)
            at androidx.sqlite.db.framework.FrameworkSQLiteOpenHelper$OpenHelper.getWritableSupportDatabase(FrameworkSQLiteOpenHelper.java:151)
            at androidx.sqlite.db.framework.FrameworkSQLiteOpenHelper.getWritableDatabase(FrameworkSQLiteOpenHelper.java:112)
            at androidx.room.RoomDatabase.inTransaction(RoomDatabase.java:705)
            at androidx.room.RoomDatabase.assertNotSuspendingTransaction(RoomDatabase.java:482)
            at a.a.so68183015kotlinroommigrationconversion8_to_9.AllDao_Impl.getAllFromMytable(AllDao_Impl.java:28)
    2021-06-30 06:51:28.172 E/AndroidRuntime:     at a.a.so68183015kotlinroommigrationconversion8_to_9.MainActivity.onCreate(MainActivity.kt:20)
            at android.app.Activity.performCreate(Activity.java:7136)
            at android.app.Activity.performCreate(Activity.java:7127)
            at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1271)
            at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2893)
                ... 11 more
    2021-06-30 06:51:28.185 I/Process: Sending signal. PID: 24101 SIG: 9
    

    您可以看到迁移已被调用。

    实际迁移

    基于上述但修改TheDatabase类并添加转换,然后:-

    这种情况下的@Entity 是:-

    @Entity(tableName = "mytable", primaryKeys = ["col1","col2"])
    data class MyTable(
        val col1: Long,
        val col2: Long,
        val col3: String
    )
    

    即在这种情况下, col2 和 col3 列都没有 NOT NULL 但 room 期望它们应该。 (查看 cmets re SQL,因为它是从生成的 java 复制而来的)。

    那么(可能有点啰嗦)TheDatabase 可能是:-

    @Database(entities = [MyTable::class],version = 9,exportSchema = false)
    abstract class TheDatabase: RoomDatabase() {
        abstract fun getAllDao(): AllDao
    
        companion object {
            private var instance: TheDatabase? = null
            private val TAG = "ROOMDBINFO"
    
            fun getInstance(context: Context): TheDatabase {
                if (instance == null) {
                    instance = Room.databaseBuilder(context,TheDatabase::class.java,"database")
                        .allowMainThreadQueries()
                        .addMigrations(MIGRATION_8_9)
                        .build()
    
                }
                return instance as TheDatabase
            }
    
            // copied from java(generated) <thisclass>_Impl.java>  (i.e. TheDatabase_Impl):-
            // From the createAllTables method
            // _db.execSQL("CREATE TABLE IF NOT EXISTS `mytable` (`col1` INTEGER NOT NULL, `col2` INTEGER NOT NULL, `col3` TEXT NOT NULL, PRIMARY KEY(`col1`, `col2`))");
            private val MIGRATION_8_9 = object: Migration(8, 9) {
                override fun migrate(database: SupportSQLiteDatabase) {
                    val inTransaction = database.inTransaction()
                    Log.d(TAG,"Performing database migration from SQLiteHelper to Room")
                    if (!inTransaction) database.beginTransaction()
                    var csr = database.query("SELECT * FROM sqlite_master")
                    DatabaseUtils.dumpCursor(csr)
                    csr.close()
                    // SEE ABOVE FROM GETTING CORRECT SQL
                    database.execSQL("CREATE TABLE IF NOT EXISTS `mytable_new` (`col1` INTEGER NOT NULL, `col2` INTEGER NOT NULL, `col3` TEXT NOT NULL, PRIMARY KEY(`col1`, `col2`))")
                    csr = database.query("SELECT coalesce(col1,0) AS col1, coalesce(col2,0) AS col2, coalesce(col3,'nothing') AS col3 FROM `mytable`")
                    DatabaseUtils.dumpCursor(csr)
                    var cv = ContentValues()
                    while (csr.moveToNext()) {
                        cv.clear()
                        cv.put("col1",csr.getLong(csr.getColumnIndex("col1")))
                        cv.put("col2",csr.getLong(csr.getColumnIndex("col2")))
                        cv.put("col3",csr.getString(csr.getColumnIndex("col3")))
                        database.insert("`mytable_new`",OnConflictStrategy.IGNORE,cv)
                    }
                    csr.close()
                    csr = database.query("SELECT * FROM sqlite_master")
                    DatabaseUtils.dumpCursor(csr)
                    csr = database.query("SELECT * FROM `mytable`")
                    while (csr.moveToNext()) {
                        Log.d(TAG,
                            "COL1 = ${csr.getLong(csr.getColumnIndex("col1"))} " +
                                    "COL2 = ${csr.getLong(csr.getColumnIndex("col2"))} " +
                                    "COL3 = ${csr.getString(csr.getColumnIndex("col3"))}"
                        )
                    }
                    csr.close()
                    database.execSQL("ALTER TABLE `mytable` RENAME TO `mytable_original`")
                    database.execSQL("ALTER TABLE `mytable_new` RENAME TO `mytable`")
                    database.execSQL("DROP TABLE IF EXISTS `mytable_original`")
                    csr = database.query("SELECT * FROM sqlite_master")
                    DatabaseUtils.dumpCursor(csr)
                    csr.close()
                    if (!inTransaction) {
                        database.setTransactionSuccessful()
                        database.endTransaction()
                    }
                }
            }
        }
    }
    

    运行时(应用程序被卸载,因此创建了原始的非 Room DB)``mytable 被转换(col2 中的 null 由于使用了合并而被转换为 0(显然你可能想要另一个值而不是 0) )。随后的运行就可以了。

    (回答太长,无法包含日志,因此您必须相信我)

    【讨论】:

    • 你是对的。有用。我的问题是我试图运行一个脚本而不是单独运行每个语句。没有错误。谢谢。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-06-19
    • 2018-03-31
    • 1970-01-01
    • 1970-01-01
    • 2019-04-13
    • 1970-01-01
    • 2020-02-17
    相关资源
    最近更新 更多