Библиотека сохранности 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