kotlin - 使用Room Persistence库,我可以设置自己的主键并自己管理它的 “uniqueness”吗?

标签 kotlin primary-key android-room

我正在使用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和编译器无法相信我不会复制主键吗?还是我想念其他东西?

提前致谢

编辑

也许我应该将此添加到我的问题中。这是让我感到困惑的代码。
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
            }
        }
    }

如您所见,我首先检查对象的存在。如果它不存在并且返回空值,那么我插入一个。如果确实存在,我就捕获它。在发布此问题之前,我没有意识到的是,最初我是将int用作主键(从1开始且没有自动生成)。该数据库中应该只有一行(记录)。然后我将其更改为使用systemID作为主键而不是int,但是我忘记更改查询:
@Query("SELECT * FROM authentication WHERE system_id = 1")
    suspend fun getAuth(): Authentication?

我意识到自己的错误,并希望将其发布给可能犯同样错误的其他人

最佳答案

even when I KNOW I haven't tried to insert another object with the same systemID



从对错误的描述以及使用REPLACE可以解决该问题之后,数据库中就有一行与您要插入的system_id相同。

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

应用程序中的对象之间的差异以及即使您在数据库中将一行等同于一个对象(它可能表示并且将在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来使用它,这很可能会使情况混乱。那就是它将找到数据并返回有效(非null)的Authentication对象。

    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
  • 删除暂停以允许从主线程运行(为方便起见)

  • 使用您的代码:-
        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加上别名)。

  • 但是,仅输出了两个身份验证对象。这是因为删除了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将在数据库中)。

    关于kotlin - 使用Room Persistence库,我可以设置自己的主键并自己管理它的 “uniqueness”吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59655934/

    相关文章:

    java - 如何销毁Kotlin中的单例对象并强制调用init block

    java - 如何在用户输入时在 EditText 中添加主题标签

    mysql - 关联三个不同表的三个主键

    java - 如何更新 Room 数据库中的现有行?

    android - room db 使用@query 更新多行

    kotlin - 编写自定义kotlin-dsl时如何忽略显式导入诸如gradle kotlin脚本之类的类

    android - 为什么我会收到此错误 "Received status code 401 from server: Unauthorized"?

    mysql - 创建一个学生ID以字母开头的表 - mysql

    mysql - 我可以创建一个没有 PK 的 InnoDB 表吗?如果是的话,是否会使用二级索引作为聚集索引?

    android - Android Room 加入的返回类型