tutorial studio room jetpack example android sqlite sqlite3 android-room android-architecture-components

jetpack - room android studio



Biblioteca de persistencia de Android Room: Upsert (7)

Debería ser posible con este tipo de declaración:

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

La biblioteca de persistencia de Android Room incluye gentilmente las anotaciones @Insert y @Update que funcionan para objetos o colecciones. Sin embargo, tengo un caso de uso (notificaciones push que contienen un modelo) que requeriría un UPSERT ya que los datos pueden o no existir en la base de datos.

Sqlite no tiene upsert de forma nativa, y las soluciones se describen en esta pregunta SO . Dadas las soluciones allí, ¿cómo se las aplicaría a Room?

Para ser más específico, ¿cómo puedo implementar una inserción o actualización en Room que no rompa ninguna restricción de clave externa? El uso de insertar con onConflict = REPLACE hará que se invoque onDelete para cualquier clave externa de esa fila. En mi caso, onDelete causa una cascada, y reinsertar una fila hará que se eliminen filas en otras tablas con la clave foránea. Este NO es el comportamiento previsto.


No pude encontrar una consulta SQLite que se inserte o actualice sin causar cambios no deseados en mi clave externa, por lo que opté por insertar primero, ignorando conflictos si ocurrieron, y actualizando inmediatamente después, nuevamente ignorando conflictos.

Los métodos de inserción y actualización están protegidos para que las clases externas vean y utilicen únicamente el método upsert. Tenga en cuenta que este no es un upsert verdadero, ya que si alguno de los POJOS de MyEntity tiene campos nulos, sobrescribirá lo que puede estar actualmente en la base de datos. Esto no es una advertencia para mí, pero puede ser para su aplicación.

@Insert(onConflict = OnConflictStrategy.IGNORE) protected abstract void insert(List<MyEntity> entities); @Update(onConflict = OnConflictStrategy.IGNORE) protected abstract void update(List<MyEntity> entities); public void upsert(List<MyEntity> entities) { insert(models); update(models); }


Otro enfoque que puedo pensar es obtener la entidad a través de DAO por consulta y luego realizar las actualizaciones deseadas. Esto puede ser menos eficiente en comparación con las otras soluciones en este hilo en términos de tiempo de ejecución debido a que tiene que recuperar la entidad completa, pero permite mucha más flexibilidad en términos de operaciones permitidas, como en qué campos / variables actualizar.

Por ejemplo :

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


Para una forma más elegante de hacerlo, sugeriría dos opciones:

Comprobación del valor de retorno de la operación de insert con IGNORE como OnConflictStrategy (si es igual a -1, significa que no se insertó la fila):

@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); } }

Manejo de la excepción de la operación de insert con FAIL como 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); } }


Quizás puedas hacer que tu BaseDao sea así.

asegure la operación upsert con @Transaction e intente actualizar solo si falla la inserción.

@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); } } }



Solo una actualización de cómo hacer esto con Kotlin que retiene los datos del modelo (tal vez para usarlo en un contador como en el ejemplo):

//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) } } }

También puede usar @Transaction y la variable del constructor de la base de datos para transacciones más complejas usando database.openHelper.writableDatabase.execSQL ("DECLARACIÓN SQL")