it-swarm.com.de

Android-Raumpersistenz-Bibliothek: Upsert

Die Raumpersistenzbibliothek von Android enthält die Anmerkungen @Insert und @Update, die für Objekte oder Sammlungen geeignet sind. Ich habe jedoch einen Anwendungsfall (Push-Benachrichtigungen, die ein Modell enthalten), für den ein UPSERT erforderlich wäre, da die Daten möglicherweise in der Datenbank vorhanden sind oder nicht.

Sqlite hat keinen nativen Upsert und Problemumgehungen werden in dieser SO - Frage beschrieben. Wie würde man sie angesichts der Lösungen dort auf Room anwenden?

Genauer gesagt, wie kann ich ein Insert oder ein Update in Room implementieren, das keine Fremdschlüsseleinschränkungen verletzt? Die Verwendung von insert mit onConflict = REPLACE bewirkt, dass onDelete für alle Fremdschlüssel dieser Zeile aufgerufen wird. In meinem Fall führt onDelete zu einer Kaskade. Durch das erneute Einfügen einer Zeile werden Zeilen in anderen Tabellen mit dem Fremdschlüssel gelöscht. Dies ist NICHT das beabsichtigte Verhalten.

41
Tunji_D

Ich konnte keine SQLite-Abfrage finden, die eingefügt oder aktualisiert werden würde, ohne dass unerwünschte Änderungen an meinem Fremdschlüssel vorgenommen wurden. Daher entschied ich mich für die erste Einfügung, ignorierte Konflikte, wenn sie aufgetreten waren, und sofort danach eine Aktualisierung, und wieder Konflikte ignorierte. 

Die Einfüge- und Aktualisierungsmethoden sind geschützt, sodass externe Klassen nur die Upsert-Methode sehen und verwenden. Beachten Sie, dass dies kein wahrer Upsert ist, da eines der MyEntity POJOS-Felder null Felder enthält. Sie überschreiben, was sich derzeit in der Datenbank befindet. Dies ist kein Nachteil für mich, aber möglicherweise für Ihre Anwendung.

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

Für einen eleganteren Weg würde ich zwei Optionen vorschlagen:

Überprüfung des Rückgabewerts von insert mit IGNORE als OnConflictStrategy (wenn -1 gleich -1 ist, bedeutet dies, dass die Zeile nicht eingefügt wurde):

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

Ausnahme von insert-Operation mit FAIL als OnConflictStrategy behandeln:

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

Vielleicht kannst du deinen BaseDao so machen.

sichern Sie den Upsert-Vorgang mit @Transaction, und versuchen Sie, nur zu aktualisieren, wenn das Einfügen fehlgeschlagen ist.

@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);
        }
    }
}
27
yeonseok.seo

Wenn die Tabelle mehr als eine Spalte enthält, können Sie verwenden

@Insert (onConflict = OnConflictStrategy.REPLACE)

eine Reihe ersetzen. 

Referenz - Gehe zu Tipps Android Room Codelab

4
Vikas Pandey

Nur ein Update, wie Kotlin die Daten des Modells beibehalten kann (möglicherweise zur Verwendung in einem Zähler wie im Beispiel):

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

Sie können @Transaction und die Datenbankkonstruktorvariable auch für komplexere Transaktionen verwenden, indem Sie database.openHelper.writableDatabase.execSQL ("SQL STATEMENT") verwenden.

2
emirua

Dies ist der Code in 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)
  }

}

1
Sam

Sollte mit dieser Art von Aussage möglich sein:

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

Ein anderer Ansatz, den ich mir vorstellen kann, ist, die Entität über DAO per Abfrage abzurufen und dann die gewünschten Aktualisierungen durchzuführen .. Dies kann im Vergleich zu den anderen Lösungen in diesem Thread hinsichtlich der Laufzeit weniger effizient sein, da das vollständige Abrufen erforderlich ist Entität, ermöglicht jedoch weitaus mehr Flexibilität hinsichtlich der zulässigen Operationen, z. B. hinsichtlich der zu aktualisierenden Felder/Variablen.

Zum Beispiel :

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