Library persistensi Room Android dengan anggun menyertakan anotasi @Insert dan @Update yang berfungsi untuk objek atau koleksi. Namun saya memiliki kasus penggunaan (pemberitahuan push yang berisi model) yang akan memerlukan UPSERT karena datanya mungkin ada atau mungkin tidak ada dalam database.
Sqlite tidak memiliki upsert secara asli, dan penyelesaiannya dijelaskan dalam pertanyaan SO ini . Mengingat solusi yang ada, bagaimana cara menerapkannya ke Room?
Untuk lebih spesifiknya, bagaimana cara menerapkan penyisipan atau pembaruan di Room yang tidak akan merusak batasan kunci asing? Menggunakan sisipkan dengan onConflict = REPLACE akan menyebabkan onDelete untuk kunci asing apa pun ke baris itu dipanggil. Dalam kasus saya onDelete menyebabkan kaskade, dan memasukkan kembali baris akan menyebabkan baris di tabel lain dengan kunci asing dihapus. Ini BUKAN perilaku yang diinginkan.
Untuk cara yang lebih elegan untuk melakukannya, saya menyarankan dua opsi:
Memeriksa nilai kembali dari
insert
operasi denganIGNORE
sebagaiOnConflictStrategy
(jika sama dengan -1 maka itu berarti baris tidak dimasukkan):@Insert(onConflict = OnConflictStrategy.IGNORE) long insert(Entity entity); @Update(onConflict = OnConflictStrategy.IGNORE) void update(Entity entity); @Transaction public void upsert(Entity entity) { long id = insert(entity); if (id == -1) { update(entity); } }
Menangani pengecualian dari
insert
operasi denganFAIL
sebagaiOnConflictStrategy
:@Insert(onConflict = OnConflictStrategy.FAIL) void insert(Entity entity); @Update(onConflict = OnConflictStrategy.FAIL) void update(Entity entity); @Transaction public void upsert(Entity entity) { try { insert(entity); } catch (SQLiteConstraintException exception) { update(entity); } }
sumber
upsert
metode tersebut dengan@Transaction
penjelasan - stackoverflow.com/questions/45677230/…Saya tidak dapat menemukan kueri SQLite yang akan menyisipkan atau memperbarui tanpa menyebabkan perubahan yang tidak diinginkan pada kunci asing saya, jadi saya memilih untuk menyisipkan terlebih dahulu, mengabaikan konflik jika terjadi, dan memperbarui segera setelah itu, lagi-lagi mengabaikan konflik.
Metode penyisipan dan pembaruan dilindungi sehingga kelas eksternal hanya melihat dan menggunakan metode upsert. Perlu diingat bahwa ini bukan upert yang sebenarnya karena jika salah satu POJOS MyEntity memiliki bidang nol, mereka akan menimpa apa yang saat ini mungkin ada di database. Ini bukan peringatan bagi saya, tetapi mungkin untuk aplikasi Anda.
@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); }
sumber
upsert
metode dengan@Transaction
anotasiJika tabel memiliki lebih dari satu kolom, Anda dapat menggunakan
@Insert(onConflict = OnConflictStrategy.REPLACE)
untuk mengganti baris.
Referensi - Buka tips Android Room Codelab
sumber
deferred = true
entitas dengan kunci asing.Ini adalah kode di Kotlin:
@Insert(onConflict = OnConflictStrategy.IGNORE) fun insert(entity: Entity): Long @Update(onConflict = OnConflictStrategy.REPLACE) fun update(entity: Entity) @Transaction fun upsert(entity: Entity) { val id = insert(entity) if (id == -1L) { update(entity) } }
sumber
null values
mana saya tidak ingin memperbarui dengan null tetapi mempertahankan nilai lama. ?Hanya pembaruan tentang cara melakukan ini dengan Kotlin yang menyimpan data model (Mungkin menggunakannya di penghitung seperti pada contoh):
//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) } } }
Anda juga dapat menggunakan @Transaction dan variabel konstruktor database untuk transaksi yang lebih kompleks menggunakan database.openHelper.writableDatabase.execSQL ("SQL STATEMENT")
sumber
Pendekatan lain yang dapat saya pikirkan adalah mendapatkan entitas melalui DAO menurut kueri, lalu melakukan pembaruan yang diinginkan. Ini mungkin kurang efisien dibandingkan dengan solusi lain di thread ini dalam hal runtime karena harus mengambil entitas penuh, tetapi memungkinkan lebih banyak fleksibilitas dalam hal operasi yang diizinkan seperti pada bidang / variabel apa yang akan diperbarui.
Sebagai contoh :
private void upsert(EntityA entityA) { EntityA existingEntityA = getEntityA("query1","query2"); if (existingEntityA == null) { insert(entityA); } else { entityA.setParam(existingEntityA.getParam()); update(entityA); } }
sumber
Seharusnya mungkin dengan pernyataan semacam ini:
INSERT INTO table_name (a, b) VALUES (1, 2) ON CONFLICT UPDATE SET a = 1, b = 2
sumber
ON CONFLICT UPDATE SET a = 1, b = 2
tidak didukung olehRoom
@Query
anotasi.Jika Anda memiliki kode warisan: beberapa entitas di Java dan
BaseDao as Interface
(di mana Anda tidak dapat menambahkan badan fungsi) atau Anda terlalu malas untuk mengganti semuaimplements
denganextends
untuk Java-anak.Akhirnya solusi malas adalah menambahkan dua
Kotlin Extension functions
:fun <T> BaseDao<T>.upsert(entityItem: T) { if (insert(entityItem) == -1L) { update(entityItem) } } fun <T> BaseDao<T>.upsert(entityItems: List<T>) { val insertResults = insert(entityItems) val itemsToUpdate = arrayListOf<T>() insertResults.forEachIndexed { index, result -> if (result == -1L) { itemsToUpdate.add(entityItems[index]) } } if (itemsToUpdate.isNotEmpty()) { update(itemsToUpdate) } }
sumber