使用房间持久性库,我可以设置自己的主键并自行管理其"唯一性"吗?



我正在使用房间库。我的一个实体的编码如下:

@Entity
data class Authentication (
@PrimaryKey
@ColumnInfo(name = "system_id")
val systemID: String = "demo",
@ColumnInfo(name = "password")
val password: String? = "demo",
@ColumnInfo(name = "server_address")
val serverAddress: String? = "https://www.someSite.com/login/"
)

使systemID为主键。这是我的Dao

@Dao
interface AuthDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertAuth(auth: Authentication): Long 
@Query("SELECT * FROM authentication WHERE system_id = 1")
suspend fun getAuth(): Authentication?
@Update
suspend fun updateAuth(auth: Authentication): Int
@Delete
suspend fun deleteAuth(auth: Authentication)
}

我正在服务器上管理系统 ID。我知道他们都是独一无二的。但是,当我尝试插入一个对象时,我的代码抛出并异常指出我的主键失败,这是唯一的约束。当我插入onConflict = OnConflictStrategy.REPLACE时(如上面的代码所示(,一切正常,但在我放置它之前,我的应用程序崩溃了。

我检查了文档,并没有真正找到问题的答案。我想知道为什么即使我知道我没有尝试插入具有相同系统ID的另一个对象,也会抛出此错误。房间不信任我吗?是因为我不允许 Room 自动生成主键,因此 Room 和编译器无法信任我不会复制主键吗?还是我错过了别的东西?

提前致谢

编辑

也许我应该把这个添加到我的问题中。这是让我感到困惑的代码。

fun checkForAuthenticationInDatabase() {
launch(IO){
var auth = AuthDatabase(getApplication()).authDao().getAuth()
if (auth == null){
Log.d("TAG", "auth was null")
auth = Authentication()
AuthDatabase(getApplication()).authDao().insertAuth(auth)
Log.d("TAG", auth.toString())
}
withContext(Main){
authentication.value = auth
}
}
}

如您所见,我首先检查对象是否存在。如果它不存在并且返回 null,那么我插入一个。如果它确实存在,我就抓住它。在发布这个问题之前我没有抓住的是,最初我使用 int 作为主键(从 1 开始,没有自动生成(。此数据库中应该只有一行(记录(。然后我将其更改为使用 systemID 作为主键而不是 int,但我忘记更改查询:

@Query("SELECT * FROM authentication WHERE system_id = 1")
suspend fun getAuth(): Authentication?

我意识到了我的错误,并想为可能犯同样错误的其他人发布此

内容

即使我知道我没有尝试插入另一个具有相同系统ID的对象

根据您对错误的描述以及使用 REPLACE 修复问题,数据库中有一行与您尝试插入的system_id相同。

请注意,给出的描述与您的描述略有不同。SQLite,Room 基本上是一个包装器,它没有存储对象的概念,而是将数据存储在行中,每行包含相同数量的元素(尽管有些可能是空的(。

应用程序中的对象之间的区别,即使您将数据库中的行等同于对象(它可能在 Room 中表示并将表示(,在数据库中数据会持久化,并在应用程序重新启动时保留在数据库中。也许正是这种坚持让你感到困惑。

如果您更改/使用

@Query("SELECT * FROM authentication WHERE system_id = :system_id")
suspend fun getAuth(system_id: String): Authentication?
  • 拥有@Query("SELECT * FROM authentication WHERE system_id = 1") suspend fun getAuth(): Authentication?是非常无用的,因为它只允许您从数据库中提取 1 个身份验证,一个可能不存在
  • 的身份验证。

并在插入之前使用它,使用您正在插入的system_id,这很可能会确认情况。也就是说,它将找到数据并返回一个有效的(非空(身份验证对象。

房间不信任我吗? 在某种程度上是的,在某种程度上不是。

是因为我不允许 Room 自动生成主键,因此 Room 和编译器无法信任我不会复制主键吗?

不。您的代码运行良好,并且在给定时允许唯一性。

还是我错过了别的东西? 如上所述,我认为情况确实如此。同样使用唯一值system_id代码工作正常(这确实假设没有其他实体(。

考虑以下哪个条形 -

  • @Query("SELECT * FROM authentication") fun getAllAuth(): List<Authentication>添加到身份验证道
  • 删除挂起以允许从主线程运行(为方便起见(

使用您的代码:-

val authDatabase = Room.databaseBuilder(this,AuthDatabase::class.java,"authdb")
.allowMainThreadQueries()
.build()
var a1 = Authentication("system_id1","password","www.server.etc")
var a2 = Authentication("system_id2","password","xxx.server.etc")
Log.d("AUTHINSERT","Insert of Authentication returned " + authDatabase.authDao().insertAuth(a1))
Log.d("AUTHINSERT","Insert of Authentication returned " + authDatabase.authDao().insertAuth(a2))
Log.d("AUTHINSERT","Insert of Authentication returned " + authDatabase.authDao().insertAuth(a1))
var authentications = authDatabase.authDao().getAllAuth()
for (authentication in authentications) {
Log.d("AUTHINFO",authentication.systemID)
}

首次运行此操作会导致日志包含:-

2020-01-09 16:39:58.351 D/AUTHINSERT: Insert of Authentication returned 1
2020-01-09 16:39:58.352 D/AUTHINSERT: Insert of Authentication returned 2
2020-01-09 16:39:58.354 D/AUTHINSERT: Insert of Authentication returned 3
2020-01-09 16:39:58.358 D/AUTHINFO: system_id2
2020-01-09 16:39:58.358 D/AUTHINFO: system_id1

也就是说,似乎已经插入了三行,其中 rowid 的 1-3。

  • rowid 是一个通常隐藏的行,具有 SQLite sqlite 提供的唯一整数(64 位有符号((除非 rowid 是锯齿的(。

但是,仅输出了 2 个身份验证对象。这是因为已删除 rowid 为 1 的行,并添加了 rowid 为 3 的行。这就是 REPLACE 的作用。

如果上面的代码再次运行,则结果为:-

2020-01-09 16:44:25.455 D/AUTHINSERT: Insert of Authentication returned 4
2020-01-09 16:44:25.456 D/AUTHINSERT: Insert of Authentication returned 5
2020-01-09 16:44:25.458 D/AUTHINSERT: Insert of Authentication returned 6
2020-01-09 16:44:25.462 D/AUTHINFO: system_id2
2020-01-09 16:44:25.462 D/AUTHINFO: system_id1

也就是说,数据库中的数据已被保留,并且因为对象中的数据(尽管它们与对象不是垃圾收集和删除的对象不同(是相同的。这两行已被删除并插入了新行(rowid 的 5 和 6 将在数据库中(。

最新更新