首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >使用NULL列但不带defalut值的房间自动迁移混淆

使用NULL列但不带defalut值的房间自动迁移混淆
EN

Stack Overflow用户
提问于 2022-05-24 07:53:07
回答 2查看 898关注 0票数 0

短版

在将Android版本从2.2.x升级到2.4.x之后,我决定使用自动迁移功能来帮助我编写更少的代码。因此,我不赞成手动编写的所有迁移,而是使用自动迁移。但是我在使用自动迁移时遇到了一个错误:

代码语言:javascript
运行
复制
// Compile Time Error: 
// New NOT NULL column'height' added with no default value specified. 
// Please specify the default value using @ColumnInfo.

@ColumnInfo(name = "height")
val height: Long = 0L

即使我以这种方式指定了默认值,但仍然得到相同的错误:

代码语言:javascript
运行
复制
@ColumnInfo(name = "height", defaultValue = "0")
val height: Long = 0L

我的代码有什么问题,我如何修复这个错误?

长版

我有两个版本数据库:

版本1

代码语言:javascript
运行
复制
@Entity(tableName = "user")
data class User(
    @PrimaryKey(autoGenerate = true)
    @ColumnInfo(name = "id")
    val id: Long = 0L,
    @ColumnInfo(name = "name")
    val name: String = ""
)

@Database(
    entities = [User::class],
    version = 1
)
abstract class UserDB : RoomDatabase()

然后,在版本2中,我在用户表中添加了一个名为“高度”的列:

第2版

代码语言:javascript
运行
复制
@Entity(tableName = "user")
data class User(
    @PrimaryKey(autoGenerate = true)
    @ColumnInfo(name = "id")
    val id: Long = 0L,
    @ColumnInfo(name = "name")
    val name: String = "",
    @Column(name = "height")
    val height: Long = 0L
)

@Database(
    entities = [User::class],
    version = 2
)
abstract class UserDB : RoomDatabase() {
    object ManualMigrations {
        val M_1_2 = object : Migration(1, 2) {
            override fun migrate(database: SupportSQLiteDatabase) {
                database.execSQL("ALTER TABLE user ADD COLUMN height INTEGER NOT NULL DEFAULT 0")
            }
        }
    }
}

现在,我想放弃all手动迁移,转而使用自动迁移。我更改了代码,得到了一个错误:

代码语言:javascript
运行
复制
// Compile Time Error: 
// New NOT NULL column'height' added with no default value specified. 
// Please specify the default value using @ColumnInfo.
@Database(
    entities = [User::class],
    autoMigrations = [
        AutoMigration(from = 1, to = 2)
    ],
    version = 2
)
abstract class UserDB : RoomDatabase()

,即使我通过@ColumnInfo指定了默认值,但仍然有相同的错误

代码语言:javascript
运行
复制
// Compile Time Error: 
// New NOT NULL column'height' added with no default value specified. 
// Please specify the default value using @ColumnInfo.
@Entity(tableName = "user")
data class User(
    @PrimaryKey(autoGenerate = true)
    @ColumnInfo(name = "id")
    val id: Long = 0L,
    @ColumnInfo(name = "name")
    val name: String = "",
    // specify default value by this way
    @Column(name = "height", defaultValue = "0")
    val height: Long = 0L
)

@Database(
    entities = [User::class],
    autoMigrations = [
        AutoMigration(from = 1, to = 3)
    ],
    // Since I have modify the structure of table, 
    // I increase the version.
    version = 3
)
abstract class UserDB : RoomDatabase()

我的代码有什么问题,我如何修复这个错误?

更新

我修改了height字段的注释

从…

代码语言:javascript
运行
复制
@Column(name = "height", defaultValue = "0")
val height: Long = 0L

代码语言:javascript
运行
复制
@ColumnInfo(name = "height", defaultValue = "0")
val height: Long = 0L

并按照上述步骤执行,但获得运行时异常。

  1. 完全卸载应用程序,并安装数据库版本=1的应用程序;
  2. 通过编写手动迁移将新列高度添加注释:@ColumnInfo(name = "height")和数据库版本更新到版本2。然后运行新版本应用程序;
  3. 以使用自动迁移,我删除手动迁移和更新高度字段注释:@ColumnInfo(name = "height", defaultValue = "0") (如果我不指定默认值,空间编译器将告诉我必须通过在编译期间抛出异常来指定默认值),并将版本从2提高到3,并将autoMigrations指定为autoMigrations。

代码语言:javascript
运行
复制
@Database(
    entities = [Worker::class],
    autoMigrations = [AutoMigration(from = 1, to = 3)],
    version = 3
)
abstract class UserDB : RoomDatabase()

然后在运行时执行这些代码并获得一个运行时异常:

代码语言:javascript
运行
复制
val userDB = buildUserDB().openHelper.readableDatabase
            Log.d(TAG, "columnNames=${workerDB.query("SELECT * FROM user").columnNames.toList()}")

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

回答 2

Stack Overflow用户

发布于 2022-05-30 10:00:39

我已经找到了这个问题的根源。

短版

当房间编译器通过将1.json(模式json文件)与2.json进行比较而生成migration_1_2时,而不是在生成migration_5_6或其他迁移时,就会引发此异常。

抛出此异常的原因是: 1.json没有that列,而2.json有nonNull while列,但是没有来自@ColumnInfo的默认值。

因为修改2.json (历史模式json文件)是危险的,所以我放弃了​​用自动迁移代替手动迁移的想法。

长版

让我在Room Compiler上展示一段代码来解释为什么会出现这个问题:

代码语言:javascript
运行
复制
// SchemaDiffer.kt

class SchemaDiffer(...) {

    ...

    private fun processAddedTableAndColumns(...) {
        toColumns.values.forEach { toColumn ->
            val match = fromColumns[toColumn.columnName]
            if (match == null) {
                if (toColumn.isNonNull && toColumn.defaultValue == null) {
                    // >>>>>>> ** HERE! ** >>>>>>>
                    // New NOT NULL column'height' added with no default value specified.
                    diffError(
                        newNotNullColumnMustHaveDefaultValue(toColumn.columnName)
                    )
                }
            }
        }
    }
}

这段代码告诉我,如果新列是nonNull ,但是在执行从旧数据库到新数据库的时,它没有被指定为默认值,那么Room编译器将抛出一个异常。

看看我的需求和代码:

我想用auto-migration.房间取代ALL手动迁移

我的密码是:

代码语言:javascript
运行
复制
// column declaration
// a non null type long
// and has not specified default value on @ColumnInfo
@ColumnInfo(name = "height")
val height: Long = 0L

当Room Compiler处理@ColumnInfoheight字段上注释以生成migration_1_to_2时,它会发现height字段it nonNull (by XType.nullability)并且没有指定默认值(通过读取@ColumnInfo.defalutValue)。

然后,通过对1.json和2.json进行比较,发现height新的列。

有一个矛盾:height列是nonNull,但它没有默认值!为了让开发人员知道这个问题,空间编译器选择抛出异常。

通过分析源代码,我们知道当空间编译器生成migration_1_to_2时抛出异常,而不是在生成migration_5_to_6或其他迁移时抛出异常。因此,即使我们为height指定了默认值,房间编译器仍然会抛出这个问题--当前版本是6,房间编译器将生成6.json,而不是2.json,默认规范不会写到2.json。

我认为修改历史模式json文件是危险的,所以我放弃了​​用自动迁移代替手动迁移的想法。

票数 2
EN

Stack Overflow用户

发布于 2022-05-24 09:39:24

,即使我通过@ColumnInfo指定了默认值,但仍然有相同的错误:

我相信(根据较长的版本)您的问题是使用的是@Column(....而不是@ColumnInfo(....

所以改变

代码语言:javascript
运行
复制
@Column(name = "height", defaultValue = "0")
val height: Long = 0L

代码语言:javascript
运行
复制
@ColumnInfo(name = "height", defaultValue = "0")
val height: Long = 0L

使用第1版和第2版(使用更正的@ColumnInfo)进行测试,使用两次运行-一次在版本1,然后使用2次运行,使用:-

代码语言:javascript
运行
复制
class MainActivity : AppCompatActivity() {
    lateinit var db: UserDB
    lateinit var dao: UserDao
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        db = UserDB.getInstance(this)
        dao = db.getUserDao()

        dao.insert(User(name = "NAME001Version$DATABASE_VERSION"))
        dao.insert(User(name = "NAME002Version$DATABASE_VERSION"))
        dao.insert(User(name = "NAME003Version$DATABASE_VERSION"))

        for (u in dao.getAllUsers()) {
            Log.d("DBINFO","User ID = ${u.id} UserName is ${u.name} Version is ${DATABASE_VERSION}")
        }
    }
}

  • ,其中DATABASE_VERSION是#

版本

通过应用程序检查的数据库是:-

这两种记录都是:-

代码语言:javascript
运行
复制
2022-05-24 19:32:57.784 D/DBINFO: User ID = 1 UserName is NAME001Version1 Version is 1
2022-05-24 19:32:57.784 D/DBINFO: User ID = 2 UserName is NAME002Version1 Version is 1
2022-05-24 19:32:57.784 D/DBINFO: User ID = 3 UserName is NAME003Version1 Version is 1



2022-05-24 19:38:41.963 D/DBINFO: User ID = 1 UserName is NAME001Version1 Version is 2
2022-05-24 19:38:41.963 D/DBINFO: User ID = 2 UserName is NAME002Version1 Version is 2
2022-05-24 19:38:41.964 D/DBINFO: User ID = 3 UserName is NAME003Version1 Version is 2
2022-05-24 19:38:41.964 D/DBINFO: User ID = 4 UserName is NAME001Version2 Version is 2
2022-05-24 19:38:41.964 D/DBINFO: User ID = 5 UserName is NAME002Version2 Version is 2
2022-05-24 19:38:41.964 D/DBINFO: User ID = 6 UserName is NAME003Version2 Version is 2

要复制该问题(必须为@Column添加一个基本注释),然后:-

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/72359140

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档