Biblioteka trwałości Room systemu Android łaskawie zawiera adnotacje @Insert i @Update, które działają dla obiektów lub kolekcji. Mam jednak przypadek użycia (powiadomienia push zawierające model), który wymagałby przesłania UPSERT, ponieważ dane mogą, ale nie muszą istnieć w bazie danych.
Sqlite nie ma natywnego upsert, a obejścia są opisane w tym pytaniu SO . Biorąc pod uwagę dostępne tam rozwiązania, jak można je zastosować do Pokoju?
Mówiąc dokładniej, w jaki sposób mogę zaimplementować wstawianie lub aktualizację w pokoju, która nie złamałaby żadnych ograniczeń klucza obcego? Użycie insert z onConflict = REPLACE spowoduje wywołanie onDelete dla dowolnego klucza obcego w tym wierszu. W moim przypadku onDelete powoduje kaskadę, a ponowne wstawienie wiersza spowoduje usunięcie wierszy w innych tabelach z kluczem obcym. NIE jest to zamierzone zachowanie.
Dla bardziej eleganckiego sposobu, sugerowałbym dwie opcje:
Sprawdzanie zwracanej wartości z
insert
operacjiIGNORE
jako aOnConflictStrategy
(jeśli jest równa -1, oznacza to, że wiersz nie został wstawiony):@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); } }
Obsługa wyjątku od
insert
operacjiFAIL
jakoOnConflictStrategy
:@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); } }
źródło
upsert
metodę@Transaction
adnotacją - stackoverflow.com/questions/45677230/…Nie mogłem znaleźć zapytania SQLite, które wstawiłoby lub zaktualizowałoby się bez powodowania niechcianych zmian w moim kluczu obcym, więc zamiast tego zdecydowałem się wstawić najpierw, ignorując konflikty, jeśli wystąpią, i aktualizując natychmiast po tym, ponownie ignorując konflikty.
Metody insert i update są chronione, więc klasy zewnętrzne widzą i używają tylko metody upsert. Należy pamiętać, że to nie jest prawdziwy upsert, ponieważ którykolwiek z MyEntity POJOS ma puste pola, nadpisze to, co może obecnie znajdować się w bazie danych. Nie jest to dla mnie zastrzeżenie, ale może być związane z Twoją aplikacją.
@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); }
źródło
upsert
metodę z@Transaction
adnotacjąJeśli tabela ma więcej niż jedną kolumnę, możesz użyć
@Insert(onConflict = OnConflictStrategy.REPLACE)
zastąpić wiersz.
Odniesienie - Przejdź do wskazówek Android Room Codelab
źródło
deferred = true
jednostki z kluczem obcym.Oto kod w Kotlinie:
@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) } }
źródło
null values
której nie chcę aktualizować z wartością null, ale zachowuję starą wartość. ?Tylko aktualizacja, jak to zrobić z Kotlinem zachowującym dane modelu (może użyć go w liczniku, jak na przykładzie):
//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) } } }
Możesz również użyć @Transaction i zmiennej konstruktora bazy danych dla bardziej złożonych transakcji, używając database.openHelper.writableDatabase.execSQL („SQL STATEMENT”)
źródło
Innym podejściem, które przychodzi mi do głowy, jest pobranie jednostki za pośrednictwem DAO za pomocą zapytania, a następnie wykonanie żądanych aktualizacji. Może to być mniej wydajne w porównaniu z innymi rozwiązaniami w tym wątku pod względem czasu wykonywania z powodu konieczności pobierania całej jednostki, ale pozwala na znacznie większą elastyczność w zakresie operacji dozwolonych, takich jak pola / zmienne do aktualizacji.
Na przykład :
private void upsert(EntityA entityA) { EntityA existingEntityA = getEntityA("query1","query2"); if (existingEntityA == null) { insert(entityA); } else { entityA.setParam(existingEntityA.getParam()); update(entityA); } }
źródło
Powinno to być możliwe z takim stwierdzeniem:
INSERT INTO table_name (a, b) VALUES (1, 2) ON CONFLICT UPDATE SET a = 1, b = 2
źródło
ON CONFLICT UPDATE SET a = 1, b = 2
nie jest obsługiwany przezRoom
@Query
adnotację.Jeśli masz starszych kod: Niektóre jednostki w Javie i
BaseDao as Interface
(gdzie nie można dodać ciało funkcji) albo zbyt leniwy na zastąpienie wszystkichimplements
zextends
Java-dzieci.Wreszcie leniwym rozwiązaniem jest dodanie dwóch
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) } }
źródło