在Android Room中使用@Upsert时出现“UNIQUE约束失败”

问题描述 投票:0回答:3

获取

net.sqlcipher.database.SQLiteConstraintException: error code 19: UNIQUE constraint failed with : appointment.activity_id
:尝试使用 Android Room 进行 @Upsert 时。

我对@Upsert的理解是,如果行存在,它将更新记录,如果不存在,它将插入。

以下是该表的定义:

data class AppointmentEntity(

   @PrimaryKey
   @field:ColumnInfo(name = "activity_id")
   val activityId: Long,

   @field:ColumnInfo(name = "selected_owner")
   val selectedOwner: String,

   val activityStartTimestamp: Instant,

   val activityEndTimestamp: Instant,

   val activityRevisionTimestamp: Instant?,

   val appointmentTypeCode: AppointmentTypeCode,

   val contactName: String?,

   val activityDescription: String?,

   val recurringId: Long?,

   val cancelled: Boolean,

   val personalAppointment: Boolean,

   val outlookIndicator: Boolean,

   val allDayEvent: Boolean,

   val deleteDate: Instant?
 )

这是@Dao中的代码:

@Upsert
fun insertAppointments(appointment: List<AppointmentEntity>)

为什么我会收到唯一约束错误。现有记录不是会被更新而不是重新插入吗?

来自 Android 文档:

如果目标实体包含自动生成的 PrimaryKey,则 POJO 类不需要相同的主键字段,否则 POJO 中也必须存在主键。如果主键已经存在,则只会更新部分实体字段代表的列

android android-room upsert
3个回答
3
投票

Android room 2.5.0 和 sqlcipher 之间似乎存在冲突。当我从我的房间数据库中删除 sqlcipher SupportFactory 后,upsert 就按预期工作了。

这似乎是根本原因:https://github.com/sqlcipher/android-database-sqlcipher/issues/588。 SqlCipher 不会扩展 android.database.SQLException,因此当 Room 检查异常是否是外键/唯一键时:https://android-review.googlesource.com/c/platform/frameworks/support/+/2191947/ 9/room/room-runtime/src/main/java/androidx/room/EntityUpsertionAdapter.kt#209 它总是抛出 net.sqlcipher.database.SQLException 唯一键异常,而不是更新现有记录。


0
投票

我对@Upsert的理解是,如果行存在,它将更新记录,如果不存在,它将插入。

它应该使用您可用的代码,但修改为不使用类型转换器,它确实如此。例如使用:-

@Entity(tableName = "appointment")
data class AppointmentEntity(

    @PrimaryKey
    @field:ColumnInfo(name = "activity_id")
    val activityId: Long,
    @field:ColumnInfo(name = "selected_owner")
    val selectedOwner: String,
    val activityStartTimestamp: Long?=System.currentTimeMillis() / 1000, /*<<<< CHANGED FROM Instant */
    val activityEndTimestamp: Long?=System.currentTimeMillis() / 1000,  /*<<<< CHANGED FROM Instant */
    val activityRevisionTimestamp: Long?=System.currentTimeMillis() / 1000,  /*<<<< CHANGED FROM Instant */
    //val appointmentTypeCode: AppointmentTypeCode, /*<<<<< COMMENTED OUT */
    val contactName: String?,
    val activityDescription: String?,
    val recurringId: Long?,
    val cancelled: Boolean,
    val personalAppointment: Boolean,
    val outlookIndicator: Boolean,
    val allDayEvent: Boolean,
    val deleteDate: Long?=null  /*<<<< CHANGED FROM Instant */
)

@Database(entities = [AppointmentEntity::class], exportSchema = false, version = 1)
abstract class TheDatabase: RoomDatabase() {
    abstract fun getAllDao(): AllDao

    companion object {
        private var instance: TheDatabase?=null
        fun getInstance(context: Context): TheDatabase {
            if (instance==null) {
                instance = Room.databaseBuilder(context,TheDatabase::class.java,"the_database.db")
                    .allowMainThreadQueries()
                    .build()
            }
            return instance as TheDatabase
        }
    }
}

@Dao
interface AllDao {
    @Upsert
    fun insertAppointments(appointment: List<AppointmentEntity>)
}

和:-

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

        var appointmentEntity1 = AppointmentEntity(activityId = 1, selectedOwner = "O001", contactName = "C001", recurringId = 0, cancelled = false, activityDescription = "Desc001", personalAppointment = true, outlookIndicator = false, allDayEvent = false)
        var appointmentEntity2 = AppointmentEntity(activityId = 1, selectedOwner = "O001", contactName = "C002", recurringId = 0, cancelled = false, activityDescription = "Desc002", personalAppointment = true, outlookIndicator = false, allDayEvent = false)

        db = TheDatabase.getInstance(this)
        dao = db.getAllDao()

        dao.insertAppointments(listOf(appointmentEntity1,appointmentEntity2,appointmentEntity1,appointmentEntity2))
    }
}

应用程序检查成功运行的结果显示:-

  • 即它已经处理了 4 次 UPSERTS,并且结果数据符合预期。

问题可能出在其他地方。也许是因为您似乎正在使用 sqlcipher 或其他数据库操作。


0
投票

如果您使用的是 room 版本 < 2.6.0, it could be related with a bug fix they did in this release: https://developer.android.com/jetpack/androidx/releases/room#2.6.0-beta01

© www.soinside.com 2019 - 2024. All rights reserved.