Android Room Persistence Library:Upsert

Tun*_*i_D 78 sqlite android android-room android-architecture-components

Android的Room持久性库慷慨地包含适用于对象或集合的@Insert和@Update注释.然而,我有一个用例(包含模型的推送通知)需要UPSERT,因为数据可能存在或可能不存在于数据库中.

Sqlite本身没有upsert,并且在这个SO问题中描述了变通方法.鉴于那里的解决方案,如何将它们应用于房间?

更具体地说,如何在Room中实现不会破坏任何外键约束的插入或更新?使用带onConflict = REPLACE的insert将导致onDelete调用该行的任何外键.在我的情况下,onDelete导致级联,重新插入行将导致其他表中的行与外键被删除.这不是预期的行为.

use*_*282 71

为了更优雅的方式,我建议两个选项:

insert使用IGNOREas 检查操作的返回值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);   
    }
}
Run Code Online (Sandbox Code Playgroud)

从处理异常insert的操作与FAILOnConflictStrategy:

@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);
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 这适用于单个实体,但很难为集合实现.过滤插入的集合并从更新中过滤掉它们会很不错. (9认同)
  • @DanielWilson它取决于你的应用程序,这个答案适用于单个实体,但它不适用于我所拥有的实体列表. (2认同)
  • 无论出于何种原因,当我执行第一种方法时,插入一个已经存在的ID都会返回一个大于存在的行号,而不是-1L。 (2认同)
  • 你能解释一下为什么@Update注解有FAIL或IGNORE的冲突策略吗?在什么情况下,Room 会认为更新查询存在冲突?如果我天真地解释 Update 注释上的冲突策略,我会说当有东西要更新时就会发生冲突,因此它永远不会更新。但这不是我看到的行为。更新查询是否会出现冲突?或者,如果更新导致另一个唯一键约束失败,是否会发生冲突? (2认同)

yeo*_*seo 52

也许你可以让你的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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 但是,没有"插入for循环". (9认同)
  • 你是绝对正确的!我错过了,我以为你插入了for循环.这是一个很好的解决方案. (3认同)
  • 这是黄金.这导致我去了Florina的帖子,您应该阅读:https://medium.com/androiddevelopers/7-pro-tips-for-room-fbadea4bfbd1 - 感谢提示@ yeonseok.seo! (2认同)

Tun*_*i_D 39

我找不到会插入或更新的SQLite查询而不会对我的外键造成不必要的更改,所以我选择先插入,忽略冲突(如果它们发生),然后立即更新,再次忽略冲突.

insert和update方法受到保护,因此外部类只能查看和使用upsert方法.请记住,这不是真正的upsert,好像任何MyEntity POJOS都有空字段,它们将覆盖当前数据库中的内容.这对我来说不是一个警告,但它可能适用于您的应用程序.

@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);
}
Run Code Online (Sandbox Code Playgroud)

  • 使用`@Transaction`注释更好地标记`upsert`方法 (21认同)
  • 您可能希望提高效率并检查返回值.-1表示任何类型的冲突. (6认同)
  • 我想这样做的正确方法是询问该值是否已经在DB上(使用其主键).你可以使用abstractClass(替换dao接口)或使用调用对象的dao的类来做到这一点 (3认同)
  • @LevonVardanyan 您链接的页面中的示例显示了一种与 upsert 非常相似的方法,其中包含插入和删除。此外,我们没有将注释放在插入或更新中,而是放在包含两者的方法中。 (2认同)

Vik*_*dey 6

如果表格有多个栏,您可以使用

@Insert(onConflict = OnConflictStrategy.REPLACE)

替换一行。

参考- 转到提示Android Room Codelab

  • 请不要使用此方法。如果您有任何外键查看数据,它将触发onDelete侦听器,您可能不希望这样 (10认同)
  • @ubuntudroid 即使在刚刚测试的实体外键上设置该标志,它也不能很好地工作。一旦事务完成,删除调用仍然会进行,因为它在处理过程中不会被解除,只是在发生时不会发生,但在事务结束时仍然会发生。 (2认同)

Sam*_*Sam 6

这是 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)
  }
}
Run Code Online (Sandbox Code Playgroud)