120 votes

Bibliothèque de persistance dans les salles Android: Upsert

Android la Chambre de la persistance de la bibliothèque gracieusement comprend l' @Insérer et @mise à Jour des annotations qui travaillent pour des objets ou des collections. J'ai cependant un cas d'utilisation (notifications push contenant un modèle) qui aurait besoin d'une UPSERT comme les données peuvent ou peuvent ne pas exister dans la base de données.

Sqlite n'a pas upsert en natif, et les solutions de contournement sont décrites dans cette SORTE de question. Étant donné les solutions de là, comment pourrait-on appliquer à la Chambre?

Pour être plus précis, comment puis-je mettre en œuvre une insertion ou une mise à jour dans la Chambre qui ne se brisera pas toutes les contraintes de clé étrangère? À l'aide de l'insérer avec onConflict=REPLACE sera la cause de la onDelete pour une clé étrangère pour la ligne à être appelé. Dans mon cas, onDelete provoque une cascade, et de le réinsérer une ligne entraîne des lignes dans d'autres tables avec la clé étrangère à être supprimé. Ce n'est PAS le comportement voulu.

100voto

yeonseok.seo Points 1383

Peut-être que vous pouvez faire votre BaseDao comme ça.

sécurisez l'opération upsert avec @Transaction et essayez de ne 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);
        }
    }
}
 

86voto

user3448282 Points 957

Pour une manière plus élégante de faire cela, je suggérerais deux options:

Vérification de la valeur de retour d'opération insert avec IGNORE tant que 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);   
    }
}
 

Traitement des exceptions de insert fonctionnement avec FAIL en 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);
    }
}
 

41voto

Tunji_D Points 1517

Je ne pouvais pas trouver une requête SQLite qui permettrait d'insérer ou de mettre à jour sans provoquer de changements non désirés, à ma clé étrangère, donc à la place j'ai opté pour insérer tout d'abord, ignorer les conflits, si elles ont eu lieu, et la mise à jour immédiatement après, ignorant encore les conflits.

L'insert et méthodes de mise à jour sont protégés pour des classes externes voir et d'utiliser la upsert méthode uniquement. Gardez à l'esprit que ce n'est pas un vrai upsert que si l'un des MyEntity POJO ont des champs null, ils écraseront ce qui peut être actuellement dans la base de données. Ce n'est pas un inconvénient pour moi, mais c'est 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);

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

10voto

Vikas Pandey Points 328

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

3voto

emirua Points 400

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").

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X