如何将 Room db 从 1 迁移到 2?

How to migrate Room db from 1 to 2?

我正在尝试将我的 Room 数据库迁移到下一个版本,但我不断收到相同的错误:

java.lang.IllegalStateException: A migration from 1 to 2 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.

我的数据库版本之间的唯一区别是我添加了一个新列。迁移处理如下:

@Database(
version = 2,
entities = [Note::class],
exportSchema = true)
abstract class AppDatabase : RoomDatabase() {
abstract fun noteDao(): NoteDAO

companion object {
    fun build(context: Context) = Room.databaseBuilder(context, AppDatabase::class.java, "NotesDatabase")
        .addMigrations(MIGRATION_1_2).build()
    }
}

val MIGRATION_1_2 = object : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) {
    database.execSQL("ALTER TABLE Notes ADD COLUMN image STRING")
    }
}

我不确定我是否正确实施了。该错误告诉我必须以某种方式调用 .build()。我在 activity 中尝试使用数据库,但错误是一样的,所以我删除了那个调用。

我该如何解决这个问题?

您似乎没有调用构建函数,可能有另一种构建数据库的方法(调用 databaseBuilder)。

例如在 activity/fragment 你有类似的东西:-

class MainActivity : AppCompatActivity() {
    lateinit var db: AppDatabase
    lateinit var dao: NoteDAO
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        db = Room.databaseBuilder(this,AppDatabase::class.java,"NotesDatabase")
            .allowMainThreadQueries()
            .build()
        dao = db.noteDao()
        dao.getAllNotes()
    }
}

这在更改注释实体并将版本增加到 2 后会产生结果,例如:-

java.lang.IllegalStateException: A migration from 1 to 2 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.

相反,您需要调用 AppDatabase 的构建函数

所以上面会变成:-

class MainActivity : AppCompatActivity() {
    lateinit var db: AppDatabase
    lateinit var dao: NoteDAO
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        db = AppDatabase.build(this) //<<<<<<<<<< CHANGED
        dao = db.noteDao()
        dao.getAllNotes() // Forces database access/open
    }
}

HOWEVER,然后您将得到 Expected/Found 类似的问题:-

2022-03-21 09:15:20.385 14533-14533/a.a.so71549033kotlinroommigration E/AndroidRuntime: FATAL EXCEPTION: main
    Process: a.a.so71549033kotlinroommigration, PID: 14533
    java.lang.RuntimeException: Unable to start activity ComponentInfo{a.a.so71549033kotlinroommigration/a.a.so71549033kotlinroommigration.MainActivity}: java.lang.IllegalStateException: Migration didn't properly handle: notes(a.a.so71549033kotlinroommigration.Note).
     Expected:
    TableInfo{name='notes', columns={noteText=Column{name='noteText', type='TEXT', affinity='2', notNull=true, primaryKeyPosition=0, defaultValue='null'}, image=Column{name='image', type='TEXT', affinity='2', notNull=true, primaryKeyPosition=0, defaultValue='null'}, noteId=Column{name='noteId', type='INTEGER', affinity='3', notNull=false, primaryKeyPosition=1, defaultValue='null'}}, foreignKeys=[], indices=[]}
     Found:
    TableInfo{name='notes', columns={noteText=Column{name='noteText', type='TEXT', affinity='2', notNull=true, primaryKeyPosition=0, defaultValue='null'}, image=Column{name='image', type='STRING', affinity='1', notNull=false, primaryKeyPosition=0, defaultValue='null'}, noteId=Column{name='noteId', type='INTEGER', affinity='3', notNull=false, primaryKeyPosition=1, defaultValue='null'}}, foreignKeys=[], indices=[]}
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3449)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3601)
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:85)
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2066)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loop(Looper.java:223)
        at android.app.ActivityThread.main(ActivityThread.java:7656)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)

这是因为就空间而言,STRING 不是有效的列类型。

在上面你可以see/extract那个房间需要:-

image=Column{name='image', type='TEXT', affinity='2', notNull=true, primaryKeyPosition=0, defaultValue='null'}

但是 找到了房间:-

image=Column{name='image', type='STRING', affinity='1', notNull=false, primaryKeyPosition=0, defaultValue='null'}

Room 只接受 INTEGER、REAL、TEXT 和 BLOB 列类型(尽管 SQLite 在列类型方面更加灵活)。

类型取决于变量的类型,此外 Room 对定义的其他部分也非常具体,例如 NOT NULL 是否应该是部分以及 DEFAULT 是否应该是定义的一部分或没有。

但是,Room 允许您准确确定列定义应该是什么。如果您对实体进行更改并编译项目,那么 java 代码将由 Room 生成,这包括用于创建 table(s) 的 SQL 以及在此SQL 是预期的列定义。

从 Android Studio 项目视图中,您将看到 Java (generated),在此下方的 classes/files 内,将有一些 classes,其中之一将是 AppDatabase_Impl。在这个 class 中会有一个方法 createAlltables。在这个方法中是所有 table 的 SQL。

例如:-

  • 注意图像列,在这种情况下,被编码为var image: String
    • String 等同于 TEXT 且 NOT NULL。细绳?不会有 NOT NULL

但是,如果更改 table 并添加一个带有 NOT NULL 的列,那么 SQLite 需要提供一个不为空的 DEFAULT,根据:- f a指定了 NOT NULL 约束,则该列必须具有非 NULL 的默认值。 https://www.sqlite.org/lang_altertable.html

所以在上面的例子中,为了找到房间所期望的,那么 ALTER SQL 应该是:-

ALTER TABLE Notes ADD COLUMN image TEXT NOT NULL DEFAULT 'unknown'
  • 'unknown' 可以是任何合适的