Room存储数据时NOT NULL约束失败问题求助
解决Room存储时的SQLiteConstraintException(NOT NULL约束失败)
首先,我们来拆解你遇到的这个问题:NOT NULL constraint failed: heroes.power_id 错误的根源,在于Hero实体类中@Embedded注解的使用和Powerstates的实体类设计冲突。
问题分析
- 你在
Hero类中用@Embedded(prefix = "power_")嵌入了Powerstates,这会让Room把Powerstates的所有字段直接合并到heroes表中,其中Powerstates的id字段会被映射为heroes表的power_id列。 - 但
Powerstates本身被标记为@Entity,并且它的id是@PrimaryKey(autoGenerate = true)——在SQLite中,主键默认是NOT NULL约束的。 - 当你插入
Hero对象时,Powerstates的id值还没有生成(autoGenerate是插入powerstates表时才会自动生成的,而你并没有单独插入这个表,而是直接把字段合并到heroes表),导致power_id字段为null,触发了非空约束报错。
你尝试添加多列唯一约束没用,是因为问题的核心不是重复值,而是字段为空违反了非空规则。
解决方案
根据你的业务需求,有两种可行的修复方式:
方案一:将Powerstates改为普通数据类(适合Powerstates不需要单独存储的场景)
如果Powerstates只是Hero的属性子集,不需要单独作为一张表存储,那么应该去掉它的@Entity相关注解,改成普通数据类:
// 去掉@Entity、ForeignKey、Index等注解,改为普通数据类 data class Powerstates( // 如果不需要这个id字段,可以直接删除;如果需要,设置为可空或默认值 val id: Int? = null, // 因为不再是单独的表,heroId字段也可以设为可空或删除 val heroId: Int? = null, val intelligence: Int, val strength: Int, val speed: Int, val power: Int, val combat: Int )
这样Room在生成heroes表时,power_id会被设为可空(因为字段类型是Int?),或者你可以直接去掉id字段,彻底避免这个问题。
方案二:保留Powerstates为单独表,用外键关联Hero(适合Powerstates需要独立存储的场景)
如果Powerstates需要作为单独的表存在,那么Hero不应该用@Embedded,而是通过外键关联Powerstates的主键:
1. 修改Hero实体类
@Entity( tableName = "heroes", foreignKeys = [ ForeignKey( entity = Powerstates::class, parentColumns = ["id"], childColumns = ["power_id"], onDelete = ForeignKey.CASCADE ) ], indices = [Index("power_id")] ) class Hero( @PrimaryKey @ColumnInfo(name = "entryid") val id: String, val name: String, @ColumnInfo(name = "power_id") val powerId: Int, // 外键,关联Powerstates的id @Embedded(prefix = "bio_") val bio: Biography, @Embedded(prefix = "appearance_") val appearance: Appearance, @Embedded(prefix = "work_") val work: Work, @Embedded val image: Image )
2. 添加PowerstatesDao的插入方法
@Dao interface PowerstatesDao { @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insertPowerstate(powerstates: Powerstates): Long // 返回插入后生成的主键id }
3. 修改数据存取逻辑,先插入Powerstates再插入Hero
private suspend fun refreshLocalDataSource(heroes: List<Hero>) { localDataSource.deleteAllHeroes() localDataSource.deleteAllPowerstates() // 需要在LocalDataSource中实现该方法 for (hero in heroes) { // 先插入Powerstates,获取自动生成的id val powerId = localDataSource.insertPowerstate(hero.power).toInt() // 创建带有powerId的Hero对象 val heroWithPowerId = hero.copy(powerId = powerId) localDataSource.saveHero(heroWithPowerId) } }
这样就能保证插入Hero时,power_id字段有合法的非空值,符合SQLite的约束要求。
内容的提问来源于stack exchange,提问作者Ali
相关产品推荐
相关产品推荐

