使用 Room 持久性库,我可以设置自己的主键并自行管理它的 "uniqueness" 吗?
Using Room persistence library, can I set my own primary key and manage it's "uniqueness" on my own?
我正在使用 Room 库。我的一个实体编码如下:
@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)
}
我正在管理服务器上的 systemId。我知道它们都是独一无二的。但是,当我尝试插入一个对象时,我的代码抛出异常并指出我的主键失败,这是唯一的约束。当我插入 onConflict = OnConflictStrategy.REPLACE
(如上面的代码所示)时,一切正常,但在我将其放置在那里之前,我的应用程序崩溃了。
我查看了文档,并没有真正找到问题的答案。我想知道为什么即使我知道我没有尝试插入具有相同 systemID 的另一个对象也会抛出此错误。 Room 不信任我吗?是因为我不允许 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,那么我将插入一个。如果它确实存在,我就抓住它。在 post 提出这个问题之前我没有发现的是,最初我使用 int 作为主键(从 1 开始并且没有自动生成)。 这个数据库中应该只有一行(记录)。然后我将其更改为使用 systemID 作为主键而不是 int 但我忘了更改查询:
@Query("SELECT * FROM authentication WHERE system_id = 1")
suspend fun getAuth(): Authentication?
我意识到自己的错误并想post为其他可能犯同样错误的人
even when I KNOW I haven't tried to insert another object with the same systemID
根据您对错误的描述以及使用 REPLACE 修复了问题,数据库中有一行与您尝试插入的 system_id 相同。
请注意,给出的描述与您的略有不同。 SQLite,Room 基本上是一个包装器,没有存储对象的概念,而是将数据存储在行中,每行包含相同数量的元素(尽管有些可能为空)。
应用程序中的对象之间的区别,即使您将数据库中的一行等同于一个对象(它可能代表并将在 Room 中),是在数据库中数据被持久化并保留在数据库中当应用程序重新启动时。或许正是这份执着让你迷茫。
如果你change/use
@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,你正在插入它很可能会确认大小写。也就是说,它将找到数据和 return 一个有效的(非空)身份验证对象。
Does Room not trust me?
To some extent yes, to some extent no.
Is it because I didn't allow Room to autogenerate the primary key and thus Room and the compiler can't trust that I won't duplicate primary keys?
没有。您的代码工作正常,并且在给定时允许唯一性。
Or am I missing something else?
As explained above, I believe that this is the case. Again with unique values for system_id the code works fine (this does assume that there are no other entities).
考虑下面哪个栏-
- 将
@Query("SELECT * FROM authentication")
fun getAllAuth(): List<Authentication>
添加到 AuthDao
- 删除 suspend 以允许 运行ning 从主线程(为了方便)
使用您的代码:-
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个Authentication对象。这是因为删除了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 将在数据库中)。
我正在使用 Room 库。我的一个实体编码如下:
@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)
}
我正在管理服务器上的 systemId。我知道它们都是独一无二的。但是,当我尝试插入一个对象时,我的代码抛出异常并指出我的主键失败,这是唯一的约束。当我插入 onConflict = OnConflictStrategy.REPLACE
(如上面的代码所示)时,一切正常,但在我将其放置在那里之前,我的应用程序崩溃了。
我查看了文档,并没有真正找到问题的答案。我想知道为什么即使我知道我没有尝试插入具有相同 systemID 的另一个对象也会抛出此错误。 Room 不信任我吗?是因为我不允许 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,那么我将插入一个。如果它确实存在,我就抓住它。在 post 提出这个问题之前我没有发现的是,最初我使用 int 作为主键(从 1 开始并且没有自动生成)。 这个数据库中应该只有一行(记录)。然后我将其更改为使用 systemID 作为主键而不是 int 但我忘了更改查询:
@Query("SELECT * FROM authentication WHERE system_id = 1")
suspend fun getAuth(): Authentication?
我意识到自己的错误并想post为其他可能犯同样错误的人
even when I KNOW I haven't tried to insert another object with the same systemID
根据您对错误的描述以及使用 REPLACE 修复了问题,数据库中有一行与您尝试插入的 system_id 相同。
请注意,给出的描述与您的略有不同。 SQLite,Room 基本上是一个包装器,没有存储对象的概念,而是将数据存储在行中,每行包含相同数量的元素(尽管有些可能为空)。
应用程序中的对象之间的区别,即使您将数据库中的一行等同于一个对象(它可能代表并将在 Room 中),是在数据库中数据被持久化并保留在数据库中当应用程序重新启动时。或许正是这份执着让你迷茫。
如果你change/use
@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,你正在插入它很可能会确认大小写。也就是说,它将找到数据和 return 一个有效的(非空)身份验证对象。
Does Room not trust me? To some extent yes, to some extent no.
Is it because I didn't allow Room to autogenerate the primary key and thus Room and the compiler can't trust that I won't duplicate primary keys?
没有。您的代码工作正常,并且在给定时允许唯一性。
Or am I missing something else? As explained above, I believe that this is the case. Again with unique values for system_id the code works fine (this does assume that there are no other entities).
考虑下面哪个栏-
- 将
@Query("SELECT * FROM authentication") fun getAllAuth(): List<Authentication>
添加到 AuthDao - 删除 suspend 以允许 运行ning 从主线程(为了方便)
使用您的代码:-
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个Authentication对象。这是因为删除了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 将在数据库中)。