房间更新(如果不存在则插入)行并返回计数更改的行

Posted

技术标签:

【中文标题】房间更新(如果不存在则插入)行并返回计数更改的行【英文标题】:room update (or insert if not exist) rows and return count changed rows 【发布时间】:2018-07-09 05:57:57 【问题描述】:

我需要更新,如果不存在,则向 ROOM DB 插入行。

我做这个:productRepository.updateProducts(productsResponse.getProductItems());

还有:

@Override
public void updateProducts(final List<ProductItem> products) 
    new Thread(() -> 
        for (ProductItem item : products) 
            Product product = createProduct(item);
            productDao.insert(product);
        
    ).start();

在 DAO 中:

@Insert
void insert(Product products);

但我有方法

@Update
void update(Product product);

还有一些问题:

    这两种方法都是无效的。插入后如何返回保存的产品或布尔标志或插入计数?

    如果我尝试调用update,但我没有行,是否会插入?

    如何更新(如果不是 - 插入)行并返回计数更新或插入的行?

【问题讨论】:

你正在寻找的 UPSERT 已经在这里解决了 ***.com/a/50736568/10516463 【参考方案1】:

    @Insert 注释的方法可以返回long。这是为插入的行新生成的 ID。使用@Update 注释的方法可以返回int。这是更新的行数。

    update 将尝试使用where 子句中的主键值更新您的所有字段。如果您的实体尚未保存在数据库中,update 查询将无法找到行并且不会更新任何内容。

    您可以使用@Insert(onConflict = OnConflictStrategy.REPLACE)。这将尝试插入实体,如果存在具有相同 ID 值的现有行,它将删除它并将其替换为您尝试插入的实体。请注意,如果您使用自动生成的 ID,这意味着生成的行将具有与被替换的原始 ID 不同的 ID。如果你想保留 ID,那么你必须想出一个自定义的方法来做到这一点。

【讨论】:

这不是一个好的解决方案。它不执行插入或更新。这是一个替换,因此您将丢失未指定更新的字段。 TL;DR Sqlite 替换操作可能违反 onDelete foreignKey 约束,但更新不会。这不包括用例 insertOrUpdate(),当您替换时,如果您在表上找到主键,它会删除该行,以便假设更新可以处理 OnCascade 的外键违规。【参考方案2】:

您可以检查您的数据库是否已经存在具有特定字段的项目,例如:

@Query("SELECT id FROM items WHERE id = :id LIMIT 1")
fun getItemId(id: String): String?

@Insert
fun insert(item: Item): Long

@Update(onConflict = OnConflictStrategy.REPLACE)
fun update(item: Item): Int

Item 是您的对象,在您的代码中:

 fun insertOrUpdate(item: Item) 
            database.runInTransaction 
                val id = getItemDao().getItemId(item.id)

                if(id == null)
                    getItemDao().insert(item)
                else
                    getItemDao().update(item)
            
        

【讨论】:

每次都需要重新获取DAO吗? 只是一种语义,但这不应该是ItemDao而不是getItemDao吗?【参考方案3】:

您应该使用 Rx android Single 来解决这个问题。示例:

@Query("SELECT * FROM subjectFormTable WHERE study_subject_id ==:subjectFormId")
fun getSubjectForm(subjectFormId: Int): Single<SubjectFormEntity>

我们使用

val compositeDisposable = CompositeDisposable()

compositeDisposable.add(
            subjectFormDao.getSubjectForm(studySubjectId)
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(
                Log.e(TAG, "successful ")
            , 
                Log.e(TAG, "error "+it.message)
                //Insert or Update data here
            )
    )

【讨论】:

【参考方案4】:

您可以查看下面的方法 insertModel(),您可以在其中获取 onComplete() 和 onError() 方法:

    val db: AppDatabase = Room.databaseBuilder(mCtx, AppDatabase::class.java, "db_nam.sqlite").build()

    companion object 
        @SuppressLint("StaticFieldLeak")
        private var mInstance: DatabaseClient? = null

        @Synchronized
        fun getInstance(mCtx: Context): DatabaseClient 
            if (mInstance == null) 
                mInstance = DatabaseClient(mCtx)
            
            return mInstance as DatabaseClient
        
    

    // SEE HERE FOR INSERTING DATA SUCCESSFULLY AND ERROR CODE  
    private fun insertModel(rss_Model: RSS_Model) 
        Completable.fromAction 

            db.database_dao().insertRSS(rss_Model)

        .observeOn(AndroidSchedulers.mainThread())
                .subscribeOn(Schedulers.io()).subscribe(object : CompletableObserver 
                    override fun onSubscribe(d: Disposable) 

                    override fun onComplete() 
                        // Log.e("onComplete","GET SUCCESS HERE")
                    

                    override fun onError(e: Throwable) 
                        // Log.e("onError","onError")
                    
                )
    

【讨论】:

【参考方案5】:

正如@Danail Alexiev 所说,@Insert 可以返回一个long@Update 可以返回 int

但是您使用更新的目的是什么?如果您只想将整个对象替换为新对象,则只需指定 OnConflictStrategy

@Insert(onConflict = OnConflictStrategy.REPLACE)
void insert(Product products);

结果将是: 不存在的项目将被添加,已经存在的项目将被新的项目替换。

如果您需要只更新一个参数(例如数量),您可以这样做

  @Insert(onConflict = OnConflictStrategy.REPLACE)
  void insert(Product products);

  @Query("SELECT * from item WHERE id= :id")
  List<Product> getItemById(int id);

  @Query("UPDATE item SET quantity = quantity + 1 WHERE id = :id")
  void updateQuantity(int id)

  void insertOrUpdate(Product item) 
                List<Product> itemsFromDB = getItemById(item.getId())
                if (itemsFromDB.isEmpty())
                    insert(item)
                else
                    updateQuantity(item.getId())   
            
        

结果将是: 尝试在数据库中查找项目,如果找到则更新属性,如果不插入新项目。所以你只需要从你的 DAO 调用一个方法insertOrUpdate

【讨论】:

你是救命恩人……我正要讨厌 ROOM Db ……非常感谢。 上述代码中的返回类型存在一些问题。此外,如果查询没有记录,则列表应返回空,而不是 null。 @Rainmaker 我的荣幸 :) 为了进一步优化这一点,我们可以通过直接插入 ConflictStrategy 作为 Ignore 并检查其返回类型来节省在 DB 上执行的事务数。如果插入失败,因为记录存在,它将直接更新记录,否则插入成功。因此,通过这种方式,额外查找数据库中的记录被取消。【参考方案6】:

您还可以检查该条目是否存在于数据库中,您可以相应地制定您的逻辑

科特林

@Query("SELECT * FROM Product WHERE productId == :id")
fun isProductExist(id: Int): Boolean

Java

@Query("SELECT * FROM Product WHERE productId == :id")
public boolean isExist(int id);

【讨论】:

以上是关于房间更新(如果不存在则插入)行并返回计数更改的行的主要内容,如果未能解决你的问题,请参考以下文章

插入历史行并相应地更改任一侧的行

仅插入唯一行并更新已存在的行

如果其他表中不存在 id,则获取不同的行并使用合并

postgres:更新冲突插入行并返回旧值

使行不可变并允许插入

如何确保两行不具有相同的值[重复]