Android Oda Kalıcı Kitaplığı: Yukarı


102

Android'in Oda kalıcılığı kitaplığı, nesneler veya koleksiyonlar için çalışan @Insert ve @Update ek açıklamalarını zarif bir şekilde içerir. Bununla birlikte, veriler veritabanında mevcut olabileceği veya bulunmayabileceği için bir UPSERT gerektirecek bir kullanım durumum (bir model içeren push bildirimleri) var.

Sqlite yerel olarak yükseltmeye sahip değildir ve geçici çözümler bu SO sorusunda açıklanmıştır . Buradaki çözümler göz önüne alındığında, bunları Room'a nasıl uygularsınız?

Daha spesifik olmak gerekirse, herhangi bir yabancı anahtar kısıtlamasını bozmayacak bir ekleme veya güncellemeyi Room'da nasıl uygulayabilirim? OnConflict = REPLACE ile insert kullanmak, o satıra yönelik herhangi bir yabancı anahtar için onDelete'in çağrılmasına neden olacaktır. Benim durumumda onDelete bir basamaklamaya neden olur ve bir satırı yeniden eklemek, yabancı anahtarın bulunduğu diğer tablolardaki satırların silinmesine neden olur. Bu amaçlanan davranış DEĞİLDİR.

Yanıtlar:


81

Belki de Basedao'nuzu böyle yapabilirsiniz.

yükseltme işlemini @Transaction ile güvenli hale getirin ve yalnızca ekleme başarısız olursa güncellemeye çalışın.

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

Listedeki her öğe için birden çok veritabanı etkileşimi olacağından bu, performans açısından kötü olur.
Tunji_D

13
ancak "for döngüsüne ekleme" YOKTUR.
yeonseok.seo

4
kesinlikle haklısın! Bunu kaçırdım, for döngüsüne girdiğini sanıyordum. Bu harika bir çözüm.
Tunji_D

2
Bu altın. Bu beni Florina'nın gönderisine götürdü, okumalısınız: medium.com/androiddevelopers/7-pro-tips-for-room-fbadea4bfbd1 - ipucu @ yeonseok.seo için teşekkürler!
Benoit Duffez

1
@PRA bildiğim kadarıyla hiç önemli değil. docs.oracle.com/javase/specs/jls/se8/html/… Long, uzun kutudan çıkarılacak ve tamsayı eşitlik testi yapılacaktır. eğer yanılıyorsam lütfen beni doğru yönü göster.
yeonseok.seo

81

Bunu yapmanın daha zarif bir yolu için iki seçenek öneririm:

A insertile işlemden dönüş değeri kontrol IGNOREediliyor OnConflictStrategy(-1'e eşitse satır eklenmemiş demektir):

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

Dan istisna Handling insertile operasyon FAILbir şekilde OnConflictStrategy:

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

9
bu, tek tek varlıklar için iyi sonuç verir, ancak bir koleksiyon için uygulanması zordur. Hangi koleksiyonların eklendiğini filtrelemek ve güncellemeden onları filtrelemek güzel olurdu.
Tunji_D

2
@DanielWilson, uygulamanıza bağlıdır, bu cevap tekli varlıklar için iyi çalışıyor, ancak sahip olduğum varlıkların bir listesi için geçerli değil.
Tunji_D

2
Sebep ne olursa olsun, ilk yaklaşımı uyguladığımda, zaten var olan bir ID eklemek -1L değil, var olandan daha büyük bir satır numarası döndürür.
ElliotM

1
Ohmnibus'un diğer cevapta söylediği gibi, upsertyöntemi @Transactionek açıklama ile işaretleyin - stackoverflow.com/questions/45677230/…
Dr.jacky

@Update ek açıklamasının neden FAIL veya IGNORE çatışma stratejisine sahip olduğunu açıklayabilir misiniz? Room hangi durumlarda bir güncelleme sorgusunun yine de bir çakışma içerdiğini düşünür? Güncelleme ek açıklamasında çatışma stratejisini safça yorumlarsam, güncellenecek bir şey olduğunda bir çatışma olduğunu ve bu nedenle asla güncellenmeyeceğini söyleyebilirim. Ama gördüğüm davranış bu değil. Güncelleme sorgularında çakışma bile olabilir mi? Veya bir güncelleme başka bir benzersiz anahtar kısıtlamasının başarısız olmasına neden olursa çakışmalar ortaya çıkar mı?
Hylke

41

Yabancı anahtarımda istenmeyen değişikliklere neden olmadan eklenecek veya güncellenecek bir SQLite sorgusu bulamadım, bu yüzden ilk önce eklemeyi seçtim, eğer meydana gelirse çakışmaları yok sayarak ve hemen ardından güncellemeyi yine çakışmaları yok sayarak.

Ekleme ve güncelleme yöntemleri korumalıdır, böylece harici sınıflar yalnızca yükseltme yöntemini görür ve kullanır. Bunun, MyEntity POJOS'larından herhangi birinin boş alanları varmış gibi gerçek bir yükseltme olmadığını, veritabanında şu anda olabilecek olanın üzerine yazacaklarını unutmayın. Bu benim için bir uyarı değil, ancak başvurunuz için olabilir.

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

6
daha verimli hale getirmek ve dönüş değerlerini kontrol etmek isteyebilirsiniz. -1 ne tür olursa olsun çatışmayı gösterir.
jcuypers

21
upsertYöntemi @Transactionaçıklama ile daha iyi işaretleyin
Ohmnibus

3
Sanırım bunu yapmanın doğru yolu, değerin zaten DB'de olup olmadığını sormaktır (birincil anahtarını kullanarak). bunu bir abstractClass kullanarak (dao arayüzünü değiştirmek için) veya nesnenin arka planına çağıran sınıfı kullanarak yapabilirsiniz
Sebastian Corradi

@Ohmnibus hayır, çünkü dokümantasyon şunu söylüyor> Bu ek açıklamayı bir Ekle, Güncelle veya Sil yöntemine koymanın hiçbir etkisi yoktur çünkü bunlar her zaman bir işlem içinde çalıştırılır. Benzer şekilde, Sorgu ile açıklanmışsa, ancak bir güncelleme veya silme ifadesi çalıştırıyorsa, otomatik olarak bir işleme sarılır. İşlem belgesine bakın
Levon Vardanyan

1
@LevonVardanyan, bağladığınız sayfadaki örnek, yükseltmeye çok benzer, bir ekleme ve bir silme içeren bir yöntem göstermektedir. Ayrıca, ek açıklamayı bir ekleme veya güncellemeye değil, her ikisini de içeren bir yönteme koyuyoruz.
Ohmnibus

8

Tabloda birden fazla sütun varsa, kullanabilirsiniz

@Insert(onConflict = OnConflictStrategy.REPLACE)

bir satırı değiştirmek için.

Referans - Android Room Codelab ipuçlarına git


19
Lütfen bu yöntemi kullanmayın. Verilerinize bakan herhangi bir yabancı anahtarınız varsa, dinleyiciyi Sil'i tetikler ve muhtemelen bunu istemezsiniz
Alexandr Zhurkov

@AlexandrZhurkov, sanırım yalnızca güncellemede tetiklenmeli, o zaman bu uygulanırsa herhangi bir dinleyici bunu doğru yapacaktır. Her neyse, veriler üzerinde dinleyicimiz ve onDelete tetikleyicilerimiz varsa, o zaman kodla işlenmelidir
Vikas Pandey

@AlexandrZhurkov Bu deferred = true, varlık üzerinde yabancı anahtarla ayar yapıldığında iyi çalışır .
ubuntudroid

@ubuntudroid Yeni test edilen varlıkların yabancı anahtarına bu bayrak koyulduğunda bile iyi çalışmıyor. Silme çağrısı, işlemler tamamlandıktan sonra devam eder çünkü işlem sırasında reddedilmez, yalnızca gerçekleştiğinde değil, işlemin sonunda hala gerçekleşir.
Gölge

4

Kotlin'deki kod budur:

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

1
long id = insert (varlık), kotlin için val id = insert (varlık) olmalıdır
Kibotu

@Sam, null valuesnull ile güncellemek istemediğim ancak eski değeri korumak istemediğim yerlerle nasıl başa çıkacağım . ?
binrebin

3

Modelin Kotlin tutma verileriyle bunun nasıl yapılacağına dair bir güncelleme (Belki örnek olarak bir sayaçta kullanabilirsiniz):

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

Ayrıca, database.openHelper.writableDatabase.execSQL ("SQL STATEMENT") kullanarak daha karmaşık işlemler için @Transaction ve veritabanı yapıcı değişkenini de kullanabilirsiniz.


0

Aklıma gelen başka bir yaklaşım, varlığı DAO aracılığıyla sorguya almak ve ardından istenen güncellemeleri yapmaktır. Bu, tam varlığı almak zorunda olduğu için çalışma zamanı açısından bu iş parçacığındaki diğer çözümlere kıyasla daha az verimli olabilir, ancak hangi alanların / değişkenlerin güncelleneceği gibi izin verilen işlemler açısından çok daha fazla esneklik sağlar.

Örneğin :

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

0

Bu tür bir ifade ile mümkün olmalı:

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

Ne demek istiyorsun? ek açıklama ON CONFLICT UPDATE SET a = 1, b = 2tarafından desteklenmez Room @Query.
2019

-1

Eski kodunuz varsa: Java'daki bazı varlıklar ve BaseDao as Interface(bir işlev gövdesi ekleyemediğiniz yerlerde) veya hepsini Java çocukları implementsile değiştirmek için çok tembelsiniz extends.

Not: Yalnızca Kotlin kodunda çalışır. Kotlin'de yeni kod yazdığına eminim, değil mi? :)

Son olarak tembel bir çözüm, iki tane eklemektir 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)
    }
}

Bu kusurlu gibi görünüyor? Düzgün bir işlem oluşturmaz.
Rawa
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.