Могу ли я установить свой собственный первичный ключ и управлять его «уникальностью», используя библиотеку комнат постоянства? - PullRequest
1 голос
/ 09 января 2020

Я пользуюсь библиотекой 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)
}

Я управляю системными идентификаторами на сервере. Я знаю, что все они будут уникальными. Однако, когда я пытаюсь вставить объект, мой код выдает и исключение, заявляющее, что мой первичный ключ не прошел, это уникальное ограничение. Когда я вставляю 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?

Я осознал свою ошибку и хотел опубликовать это для других, которые могут совершить ту же ошибку

1 Ответ

1 голос
/ 09 января 2020

, даже когда я ЗНАЮ, что я не пытался вставить другой объект с тем же системным ID

Из вашего описания ошибки и того, что использование REPLACE устраняет проблему, возникает строка в базе данных с тем же system_id, который вы пытаетесь вставить.

Обратите внимание, что приведенное описание немного отличается от вашего. SQLite, для которого 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 автоматически генерировать первичный ключ и, следовательно, Room и компилятор не могут поверить, что я не буду дублировать первичные ключи?

Нет. Ваш код работает нормально и допускает уникальность, когда он предоставляется.

Или я что-то упускаю? Как объяснено выше, я считаю, что это так. Опять же, с уникальными значениями для system_id, код работает нормально (предполагается, что других сущностей нет).

Рассмотрим следующую полосу -

  • добавление @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 является обычно скрытая строка, которая имеет уникальное целое число (64-битное знаковое), предоставляемое SQLite sqlite (если только для 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 будут в базе данных).

...