Библиотека сохраняемости Room в Android любезно включает аннотации @Insert и @Update, которые работают для объектов или коллекций. Однако у меня есть вариант использования (push-уведомления, содержащие модель), для которого потребуется UPSERT, поскольку данные могут существовать или не существовать в базе данных.
Sqlite не имеет встроенного upsert, и обходные пути описаны в этом вопросе SO . Учитывая имеющиеся там решения, как их применить к Room?
Чтобы быть более конкретным, как я могу реализовать вставку или обновление в Room, которое не нарушило бы никаких ограничений внешнего ключа? Использование вставки с onConflict = REPLACE приведет к вызову onDelete для любого внешнего ключа этой строки. В моем случае onDelete вызывает каскад, и повторная вставка строки приведет к удалению строк в других таблицах с внешним ключом. Это НЕ предполагаемое поведение.
Для более элегантного способа сделать это я бы предложил два варианта:
Проверка возвращаемого значения из
insert
операции сIGNORE
asOnConflictStrategy
(если оно равно -1, значит, строка не вставлена):@Insert(onConflict = OnConflictStrategy.IGNORE) long insert(Entity entity); @Update(onConflict = OnConflictStrategy.IGNORE) void update(Entity entity); @Transaction 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); @Transaction public void upsert(Entity entity) { try { insert(entity); } catch (SQLiteConstraintException exception) { update(entity); } }
источник
upsert
метод@Transaction
аннотацией - stackoverflow.com/questions/45677230/…Мне не удалось найти SQLite-запрос, который бы вставлял или обновлялся, не вызывая нежелательных изменений моего внешнего ключа, поэтому вместо этого я решил сначала вставить, игнорируя конфликты, если они возникли, и обновлять сразу после этого, снова игнорируя конфликты.
Методы вставки и обновления защищены, поэтому внешние классы видят и используют только метод upsert. Имейте в виду, что это не настоящий апсерт, поскольку если какой-либо из MyEntity POJOS имеет пустые поля, они перезапишут то, что в настоящее время может быть в базе данных. Это не предупреждение для меня, но может быть для вашего приложения.
@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); }
источник
upsert
способ@Transaction
аннотациейЕсли в таблице более одного столбца, вы можете использовать
@Insert(onConflict = OnConflictStrategy.REPLACE)
заменить строку.
Ссылка - Перейти к советам Android Room Codelab
источник
deferred = true
сущности с внешним ключом.Это код в Котлине:
@Insert(onConflict = OnConflictStrategy.IGNORE) fun insert(entity: Entity): Long @Update(onConflict = OnConflictStrategy.REPLACE) fun update(entity: Entity) @Transaction fun upsert(entity: Entity) { val id = insert(entity) if (id == -1L) { update(entity) } }
источник
null values
где я не хочу обновлять значение null, но сохраняю старое значение. ?Просто обновление о том, как это сделать с 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»)
источник
Другой подход, который я могу придумать, - это получить объект через DAO по запросу, а затем выполнить любые желаемые обновления. Это может быть менее эффективным по сравнению с другими решениями в этом потоке с точки зрения времени выполнения из-за необходимости извлекать полную сущность, но обеспечивает гораздо большую гибкость с точки зрения разрешенных операций, таких как поля / переменные для обновления.
Например :
private void upsert(EntityA entityA) { EntityA existingEntityA = getEntityA("query1","query2"); if (existingEntityA == null) { insert(entityA); } else { entityA.setParam(existingEntityA.getParam()); update(entityA); } }
источник
Это должно быть возможно с таким утверждением:
INSERT INTO table_name (a, b) VALUES (1, 2) ON CONFLICT UPDATE SET a = 1, b = 2
источник
ON CONFLICT UPDATE SET a = 1, b = 2
не поддерживаетсяRoom
@Query
аннотацией.Если у вас есть устаревший код: некоторые сущности на Java и
BaseDao as Interface
(где вы не можете добавить тело функции) или вам лень заменить всеimplements
наextends
для Java-потомков.Наконец, ленивое решение - добавить два
Kotlin Extension functions
:fun <T> BaseDao<T>.upsert(entityItem: T) { if (insert(entityItem) == -1L) { update(entityItem) } } fun <T> BaseDao<T>.upsert(entityItems: List<T>) { val insertResults = insert(entityItems) val itemsToUpdate = arrayListOf<T>() insertResults.forEachIndexed { index, result -> if (result == -1L) { itemsToUpdate.add(entityItems[index]) } } if (itemsToUpdate.isNotEmpty()) { update(itemsToUpdate) } }
источник