首页 文章

Android Room Persistence Library:Upsert

提问于
浏览
49

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

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

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

7 回答

  • 0

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

    使用 IGNORE 作为 OnConflictStrategy 检查 insert 操作的返回值(如果它等于-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);   
        }
    }
    

    使用 FAIL 作为 OnConflictStrategy 处理 insert 操作中的异常:

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

    也许你可以让你的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);
            }
        }
    }
    
  • 4

    我找不到会插入或更新的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);
    
    public void upsert(List<MyEntity> entities) {
        insert(models);
        update(models);
    }
    
  • 0

    如果表有多个列,则可以使用

    @Insert(onConflict = OnConflictStrategy.REPLACE)

    替换一行 .

    参考 - Go to tips Android Room Codelab

  • 2

    只是更新如何使用Kotlin保留模型的数据(可能在例如计数器中使用它):

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

    您还可以使用@Transaction和数据库构造函数变量来处理更复杂的事务,使用database.openHelper.writableDatabase.execSQL(“SQL STATEMENT”)

  • 32

    我能想到的另一种方法是通过查询通过DAO获取实体,然后执行任何所需的更新 . 由于必须检索完整实体,因此在运行时方面,与此线程中的其他解决方案相比,这可能效率较低,但在允许的操作(例如,要更新的字段/变量)方面允许更大的灵活性 .

    例如 :

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

    这种说法应该是可能的:

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

相关问题