Adding a new column into sqlite produces an error: Exception while computing database live data

Adding a new column into sqlite produces an error: Exception while computing database live data

我有这个数据 class,我在其中添加了一个新值 requirePolice

@Entity
data class Crime(@PrimaryKey val id : UUID = UUID.randomUUID(),
                 var title: String ="",
                 var date: Date = Date(),
                 var isSolved: Boolean = false,
                 var requirePolice : Boolean = false, <--- this is the new value
                 var suspect: String = ""){

    //designating a picture location p.317
    val photoFileName
        get() = "IMG_$id.jpg"
}

我将数据库从 2 升级到 3 以将其包含在 SQLite 中 table,我还保留了以前的升级代码

@Database(entities = [Crime::class], version = 3)
@TypeConverters(CrimeTypeConverters::class) 
abstract class CrimeDatabase: RoomDatabase(){

    abstract fun crimeDao() : CrimeDao

}

val migration_1_2 = object : Migration(1,2){
    override fun migrate(database: SupportSQLiteDatabase) {
        database.execSQL("ALTER TABLE Crime ADD COLUMN suspect TEXT NOT NULL DEFAULT ''")
    }
}

val migration_2_3 = object : Migration(2,3){
    override fun migrate(database: SupportSQLiteDatabase) {
        database.execSQL("ALTER TABLE Crime ADD COLUMN requirePolice TEXT NOT NULL DEFAULT ''")
    }
}

我以这种方式将升级包含到存储库中

private const val DATABASE_NAME = "crime-database"

class CrimeRepository private constructor(context: Context){

    private val database: CrimeDatabase = Room.databaseBuilder(
        context.applicationContext,
        CrimeDatabase::class.java,
        DATABASE_NAME
    ).addMigrations(migration_1_2)
     .addMigrations(migration_2_3)//adding latest migration 
     .build()
...

我一直收到这个错误

E/AndroidRuntime: FATAL EXCEPTION: arch_disk_io_0
    Process: com.bignerdranch.android.criminalintent, PID: 22566
    java.lang.RuntimeException: Exception while computing database live data.
        at androidx.room.RoomTrackingLiveData.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:923)
     Caused by: java.lang.IllegalStateException: Migration didn't properly handle: Crime(com.bignerdranch.android.criminalintent.Crime).
     Expected:
    TableInfo{name='Crime', columns={date=Column{name='date', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=0, defaultValue='null'}, requirePolice=Column{name='requirePolice', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=0, defaultValue='null'}, id=Column{name='id', type='TEXT', affinity='2', notNull=true, primaryKeyPosition=1, defaultValue='null'}, suspect=Column{name='suspect', type='TEXT', affinity='2', notNull=true, primaryKeyPosition=0, defaultValue='null'}, title=Column{name='title', type='TEXT', affinity='2', notNull=true, primaryKeyPosition=0, defaultValue='null'}, isSolved=Column{name='isSolved', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=0, defaultValue='null'}}, foreignKeys=[], indices=[]}
     Found:
    TableInfo{name='Crime', columns={date=Column{name='date', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=0, defaultValue='null'}, requirePolice=Column{name='requirePolice', type='TEXT', affinity='2', notNull=true, primaryKeyPosition=0, defaultValue=''''}, isSolved=Column{name='isSolved', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=0, defaultValue='null'}, id=Column{name='id', type='TEXT', affinity='2', notNull=true, primaryKeyPosition=1, defaultValue='null'}, suspect=Column{name='suspect', type='TEXT', affinity='2', notNull=true, primaryKeyPosition=0, defaultValue='null'}, title=Column{name='title', type='TEXT', affinity='2', notNull=true, primaryKeyPosition=0, defaultValue='null'}}, foreignKeys=[], indices=[]}
        at androidx.room.RoomOpenHelper.onUpgrade(RoomOpenHelper.java:103)
        at androidx.sqlite.db.framework.FrameworkSQLiteOpenHelper$OpenHelper.onUpgrade(FrameworkSQLiteOpenHelper.java:177)
        at android.database.sqlite.SQLiteOpenHelper.getDatabaseLocked(SQLiteOpenHelper.java:416)
        at android.database.sqlite.SQLiteOpenHelper.getWritableDatabase(SQLiteOpenHelper.java:316)
        at androidx.sqlite.db.framework.FrameworkSQLiteOpenHelper$OpenHelper.getWritableSupportDatabase(FrameworkSQLiteOpenHelper.java:145)
        at androidx.sqlite.db.framework.FrameworkSQLiteOpenHelper.getWritableDatabase(FrameworkSQLiteOpenHelper.java:106)
        at androidx.room.RoomDatabase.inTransaction(RoomDatabase.java:731)
        at androidx.room.RoomDatabase.assertNotSuspendingTransaction(RoomDatabase.java:508)
        at androidx.room.RoomDatabase.query(RoomDatabase.java:551)
        at androidx.room.util.DBUtil.query(DBUtil.java:83)
        at com.bignerdranch.android.criminalintent.database.CrimeDao_Impl.call(CrimeDao_Impl.java:158)
        at com.bignerdranch.android.criminalintent.database.CrimeDao_Impl.call(CrimeDao_Impl.java:155)
        at androidx.room.RoomTrackingLiveData.run(RoomTrackingLiveData.java:90)
            ... 3 more

为什么给我这个错误? 如何让它发挥作用?

如果你使用的是sql light db,那么你必须用这种方式迁移db,

@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    
    if (oldVersion < 4) {
        db.execSQL("ALTER TABLE " + TABLE_NOTIFICATION + " ADD COLUMN " + COLUMN_DEEP_LINK + " TEXT ");
    }
}

失败的原因解释在:-

Expected:
    TableInfo{name='Crime', columns={date=Column{name='date', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=0, defaultValue='null'}, requirePolice=Column{name='requirePolice', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=0, defaultValue='null'}, id=Column{name='id', type='TEXT', affinity='2', notNull=true, primaryKeyPosition=1, defaultValue='null'}, suspect=Column{name='suspect', type='TEXT', affinity='2', notNull=true, primaryKeyPosition=0, defaultValue='null'}, title=Column{name='title', type='TEXT', affinity='2', notNull=true, primaryKeyPosition=0, defaultValue='null'}, isSolved=Column{name='isSolved', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=0, defaultValue='null'}}, foreignKeys=[], indices=[]}
     Found:
    TableInfo{name='Crime', columns={date=Column{name='date', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=0, defaultValue='null'}, requirePolice=Column{name='requirePolice', type='TEXT', affinity='2', notNull=true, primaryKeyPosition=0, defaultValue=''''}, isSolved=Column{name='isSolved', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=0, defaultValue='null'}, id=Column{name='id', type='TEXT', affinity='2', notNull=true, primaryKeyPosition=1, defaultValue='null'}, suspect=Column{name='suspect', type='TEXT', affinity='2', notNull=true, primaryKeyPosition=0, defaultValue='null'}, title=Column{name='title', type='TEXT', affinity='2', notNull=true, primaryKeyPosition=0, defaultValue='null'}}, foreignKeys=[], indices=[]}

更具体地说,预期的(实体预期的)是:-

requirePolice=Column{name='requirePolice', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=0, defaultValue='null'}

虽然 table(找到的内容)已更改为:-

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

主要区别在于,ALTER 将列类型设置为 TEXT,而不是 INTEGER 列类型(布尔值等于 INTEGER)。

  • 即预期 INTEGER(布尔值)找到 TEXT(字符串)

因此你需要使用 :-

val migration_2_3 = object : Migration(2,3){
override fun migrate(database: SupportSQLiteDatabase) {
    database.execSQL("ALTER TABLE Crime ADD COLUMN requirePolice INTEGER NOT NULL")
}

或将实体更改为 (可能不是您想要的):-

@Entity
data class Crime(@PrimaryKey val id : UUID = UUID.randomUUID(),
                 var title: String ="",
                 var date: Date = Date(),
                 var isSolved: Boolean = false,
                 var requirePolice : String = "", <--- this is the new value
                 var suspect: String = ""){

    //designating a picture location p.317
    val photoFileName
        get() = "IMG_$id.jpg"
}
  • 很可能您要更改的是 ALTER 语句而不是实体。

如何避免此类 EXPECTED/FOUND 差异 - 也就是让 Room 为您完成工作

您实际上可以确定 creating/changing 实体期望的内容,然后在成功后编译 CTRL+F9编译底层 java 生成,这实际上包含 SQL 以创建 table(s).

您可以通过选择 Android 查看来查看 SQL,找到 java(生成的)文件夹,展开它,然后展开文件夹,然后找到 class 与您的 @Database class 同名,后缀为 _Impl(CrimeDatabase_Impl 在您的例子中)在 class 中会有一个 createAllTables 方法,它包含所有 table 的 EXPECTED SQL 然后您可以 extract/copy SQL 定义更改的列并在 ALTER 语句中使用它(如果添加新实体,那么整个 table 也有 SQL)。

  • 请注意,根据警告不要更改 class 中的任何内容。

这是上面的一个例子(虽然不是你的项目):-