Androidのルーム永続化ライブラリには、オブジェクトまたはコレクションに対して機能する@Insertおよび@Updateアノテーションが含まれています。ただし、データベースにデータが存在する場合と存在しない場合があるため、UPSERTを必要とするユースケース(モデルを含むプッシュ通知)があります。
Sqliteにはネイティブにアップサートがありません。回避策はこの SO question で説明されています。そこに解決策があるとしたら、それらをどのようにルームに適用するのでしょうか?
具体的には、Roomで外部キー制約を壊さない挿入または更新を実装するにはどうすればよいですか? onConflict = REPLACEでinsertを使用すると、その行への外部キーのonDeleteが呼び出されます。私の場合、onDeleteによってカスケードが発生し、行を再挿入すると、外部キーを持つ他のテーブルの行が削除されます。これは意図した動作ではありません。
外部キーに不要な変更を引き起こさずに挿入または更新するSQLiteクエリを見つけることができなかったため、代わりに最初に挿入することを選択し、競合が発生した場合は無視し、その後すぐに更新し、再び競合を無視しました。
Insertおよびupdateメソッドは保護されているため、外部クラスはupsertメソッドのみを参照して使用します。 MyEntity POJOSのいずれかにnullフィールドがあるかのように、これは真のアップサートではないことに注意してください。それらは、現在データベースにあるものを上書きします。これは私にとって警告ではありませんが、あなたのアプリケーションのためかもしれません。
@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);
}
よりエレガントな方法を実現するには、2つのオプションをお勧めします。
insert
をIGNORE
として使用して、OnConflictStrategy
操作からの戻り値を確認します(-1に等しい場合、行が挿入されなかったことを意味します)。
@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);
}
}
insert
をFAIL
として使用した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);
}
}
おそらく、このようにBaseDaoを作成できます。
@Transactionでupsert操作を保護し、挿入が失敗した場合にのみ更新を試みます。
@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);
}
}
}
テーブルに複数の列がある場合は、使用できます
@Insert(onConflict = OnConflictStrategy.REPLACE)
行を置換します。
リファレンス- Androidルームコードラボのヒントに移動
モデルのデータを保持している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)
}
}
}
Database.openHelper.writableDatabase.execSQL( "SQL STATEMENT")を使用して、より複雑なトランザクションに@Transactionおよびデータベースコンストラクター変数を使用することもできます。
次のようなステートメントで可能になるはずです。
INSERT INTO table_name (a, b) VALUES (1, 2) ON CONFLICT UPDATE SET a = 1, b = 2
私が考えることができる別のアプローチは、クエリによってDAOを介してエンティティを取得し、必要な更新を実行することです。これは、完全なエンティティを取得する必要があるため、このスレッドの他のソリューションと比較して実行時間の面で効率が低い場合がありますが、更新するフィールド/変数などの許可された操作に関してはるかに柔軟性があります。
例えば :
private void upsert(EntityA entityA) {
EntityA existingEntityA = getEntityA("query1","query2");
if (existingEntityA == null) {
insert(entityA);
} else {
entityA.setParam(existingEntityA.getParam());
update(entityA);
}
}