Библиотека сохранности Android для Android: Upsert

Библиотека сохранности Android в комнате любезно включает аннотации @Insert и @Update, которые работают для объектов или коллекций. Однако у меня есть прецедент (push-уведомления, содержащие модель), которые потребуют UPSERT, поскольку данные могут или не могут существовать в базе данных.

Sqlite не имеет поддержки, и обходные пути описаны в этом вопросе qaru.site/info/16732/.... С учетом решений там, как можно применить их к Room?

Чтобы быть более конкретным, как я могу реализовать вставку или обновление в Комнате, которые не нарушали бы ограничений внешнего ключа? Использование insert с onConflict = REPLACE приведет к вызову onDelete для любого внешнего ключа для этой строки. В моем случае onDelete вызывает каскад, а повторная установка строки вызывает строки в других таблицах с удаленным внешним ключом. Это НЕ предназначенное поведение.

Ответ 1

Возможно, вы можете сделать свой BaseDao следующим образом.

Зафиксируйте операцию upsert с помощью @Transaction и попробуйте обновить только в случае неудачной вставки.

@Dao
public abstract class BaseDao<T> {
    /**
    * Insert an object in the database.
    *
     * @param obj the object to be inserted.
     * @return The SQLite row id
     */
    @Insert(onConflict = OnConflictStrategy.IGNORE)
    public abstract long insert(T obj);

    /**
     * Insert an array of objects in the database.
     *
     * @param obj the objects to be inserted.
     * @return The SQLite row ids   
     */
    @Insert(onConflict = OnConflictStrategy.IGNORE)
    public abstract List<Long> insert(List<T> obj);

    /**
     * Update an object from the database.
     *
     * @param obj the object to be updated
     */
    @Update
    public abstract void update(T obj);

    /**
     * Update an array of objects from the database.
     *
     * @param obj the object to be updated
     */
    @Update
    public abstract void update(List<T> obj);

    /**
     * Delete an object from the database
     *
     * @param obj the object to be deleted
     */
    @Delete
    public abstract void delete(T obj);

    @Transaction
    public void upsert(T obj) {
        long id = insert(obj);
        if (id == -1) {
            update(obj);
        }
    }

    @Transaction
    public void upsert(List<T> objList) {
        List<Long> insertResult = insert(objList);
        List<T> updateList = new ArrayList<>();

        for (int i = 0; i < insertResult.size(); i++) {
            if (insertResult.get(i) == -1) {
                updateList.add(objList.get(i));
            }
        }

        if (!updateList.isEmpty()) {
            update(updateList);
        }
    }
}

Ответ 2

Для более элегантного способа сделать это я бы предложил два варианта:

Проверка возвращаемого значения из операции insert с IGNORE как OnConflictStrategy (если оно равно -1, значит, строка не была вставлена):

@Insert(onConflict = OnConflictStrategy.IGNORE)
long insert(Entity entity);

@Update(onConflict = OnConflictStrategy.IGNORE)
void update(Entity entity);

public void upsert(Entity entity) {
    long id = insert(entity);
    if (id == -1) {
        update(entity);   
    }
}

Обработка исключений из insert операции с FAIL как OnConflictStrategy:

@Insert(onConflict = OnConflictStrategy.FAIL)
void insert(Entity entity);

@Update(onConflict = OnConflictStrategy.FAIL)
void update(Entity entity);

public void upsert(Entity entity) {
    try {
        insert(entity);
    } catch (SQLiteConstraintException exception) {
        update(entity);
    }
}

Ответ 3

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

Методы вставки и обновления защищены, поэтому внешние классы видят и используют только метод upsert. Имейте в виду, что это не совсем верно, так как если у какого-либо POJOS MyEntity есть нулевые поля, они будут перезаписывать то, что в настоящее время находится в базе данных. Это не предостережение для меня, но это может быть для вашего приложения.

@Insert(onConflict = OnConflictStrategy.IGNORE)
protected abstract void insert(List<MyEntity> entities);

@Update(onConflict = OnConflictStrategy.IGNORE)
protected abstract void update(List<MyEntity> entities);

@Transaction
public void upsert(List<MyEntity> entities) {
    insert(models);
    update(models);
}

Ответ 4

Используйте @Insert(onConflict = OnConflictStrategy.REPLACE) для реализации INSERT OR REPLACE, как показано в принятом ответе по вашему связанному вопросу.

Ответ 5

Если в таблице более одного столбца, вы можете использовать

@Insert (onConflict = OnConflictStrategy.REPLACE)

заменить ряд.

Ссылка - Перейти к советам Android Room Codelab

Ответ 6

Просто обновите, как это сделать с сохранением данных Kotlin в модели (возможно, использовать его в счетчике, как в примере):

//Your Dao must be an abstract class instead of an interface (optional database constructor variable)
@Dao
abstract class ModelDao(val database: AppDatabase) {

@Insert(onConflict = OnConflictStrategy.FAIL)
abstract fun insertModel(model: Model)

//Do a custom update retaining previous data of the model 
//(I use constants for tables and column names)
 @Query("UPDATE $MODEL_TABLE SET $COUNT=$COUNT+1 WHERE $ID = :modelId")
 abstract fun updateModel(modelId: Long)

//Declare your upsert function open
open fun upsert(model: Model) {
    try {
       insertModel(model)
    }catch (exception: SQLiteConstraintException) {
        updateModel(model.id)
    }
}
}

Вы также можете использовать переменную @Transaction и конструктор базы данных для более сложных транзакций, используя database.openHelper.writableDatabase.execSQL( "SQL SQL" )

Ответ 7

Это код в Котлине:

@Insert(onConflict = OnConflictStrategy.IGNORE)
fun insert(entity: Entity): Long

@Update(onConflict = OnConflictStrategy.REPLACE)
fun update(entity: Entity)

@Transaction
fun upsert(entity: Entity) {
  long id = insert(entity)
   if (id == -1L) {
     update(entity)
  }

}

Ответ 8

Другой подход, который я могу придумать, - получить объект через DAO по запросу, а затем выполнить любые нужные обновления. Это может быть менее эффективным по сравнению с другими решениями в этом потоке с точки зрения времени выполнения из-за необходимости извлекать полную сущность, но обеспечивает гораздо большую гибкость с точки зрения разрешенных операций, например, над тем, какие поля/переменную обновлять.

Например:

private void upsert(EntityA entityA) {
   EntityA existingEntityA = getEntityA("query1","query2");
   if (existingEntityA == null) {
      insert(entityA);
   } else {
      entityA.setParam(existingEntityA.getParam());
      update(entityA);
   }
}

Ответ 9

Должно быть возможно с таким утверждением:

INSERT INTO table_name (a, b) VALUES (1, 2) ON CONFLICT UPDATE SET a = 1, b = 2