La bibliothèque de persistance des salles d'Android inclut gracieusement les annotations @Insert et @Update qui fonctionnent pour les objets ou les collections. J'ai cependant un cas d'utilisation (notifications Push contenant un modèle) qui nécessiterait un UPSERT car les données peuvent ou non exister dans la base de données.
Sqlite n’a pas d’opération native, et des solutions de contournement sont décrites dans cette SO question . Compte tenu des solutions proposées, comment pourrait-on les appliquer à Room?
Pour être plus précis, comment puis-je implémenter une insertion ou une mise à jour dans Room qui ne briserait aucune contrainte de clé étrangère? Si vous utilisez insert avec onConflict = REPLACE, onDelete appellera toute clé étrangère de cette ligne. Dans mon cas, onDelete provoque une cascade et la réinsertion d'une ligne entraîne la suppression des lignes d'autres tables avec la clé étrangère. Ce n'est pas le comportement prévu.
Je ne pouvais pas trouver une requête SQLite qui insère ou met à jour sans provoquer de modifications indésirables de ma clé étrangère. J'ai donc opté pour l'insertion d'abord, en ignorant les conflits s'ils se produisaient et en effectuant une mise à jour immédiatement après, en ignorant à nouveau les conflits.
Les méthodes insert et update étant protégées, les classes externes voient et utilisent uniquement la méthode upsert. Gardez à l'esprit que ce n'est pas un véritable upsert, comme si l'un des POJOS de MyEntity avait des champs nuls, ils écraseraient ce qui pourrait se trouver dans la base de données. Ce n'est pas une mise en garde pour moi, mais peut-être pour votre application.
@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);
}
Pour une manière plus élégante de le faire, je suggérerais deux options:
Vérification de la valeur de retour de l'opération insert
avec IGNORE
comme OnConflictStrategy
(si elle est égale à -1, cela signifie que la ligne n'a pas été insérée):
@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);
}
}
Gestion des exceptions de l'opération insert
avec FAIL
en tant que 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);
}
}
Peut-être que vous pouvez faire votre BaseDao comme ça.
sécurisez l'opération upsert avec @Transaction, et essayez de mettre à jour que si l'insertion a échoué.
@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);
}
}
}
Si la table a plus d'une colonne, vous pouvez utiliser
@Insert (onConflict = OnConflictStrategy.REPLACE)
pour remplacer une rangée.
Référence - Allez aux conseils Android Room Codelab
Juste une mise à jour pour savoir comment faire cela avec Kotlin en conservant les données du modèle (peut-être pour l'utiliser dans un compteur comme dans l'exemple):
//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)
}
}
}
Vous pouvez également utiliser @Transaction et la variable constructeur de la base de données pour des transactions plus complexes à l'aide de database.openHelper.writableDatabase.execSQL ("SQL STATEMENT").
Ceci est le code dans Kotlin:
@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)
}
}
Devrait être possible avec ce genre de déclaration:
INSERT INTO table_name (a, b) VALUES (1, 2) ON CONFLICT UPDATE SET a = 1, b = 2
Une autre approche à laquelle je peux penser est d’obtenir l’entité via DAO par requête, puis d’effectuer les mises à jour souhaitées. entité, mais permet beaucoup plus de flexibilité en termes d'opérations autorisées telles que les champs/variables à mettre à jour.
Par exemple :
private void upsert(EntityA entityA) {
EntityA existingEntityA = getEntityA("query1","query2");
if (existingEntityA == null) {
insert(entityA);
} else {
entityA.setParam(existingEntityA.getParam());
update(entityA);
}
}