Android Room Persistence Library: Upsert

前端 未结 9 574
太阳男子
太阳男子 2020-12-12 10:50

Android\'s Room persistence library graciously includes the @Insert and @Update annotations that work for objects or collections. I however have a use case (push notificatio

相关标签:
9条回答
  • 2020-12-12 11:27

    If the table has more than one column, you can use

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    

    to replace a row.

    Reference - Go to tips Android Room Codelab

    0 讨论(0)
  • 2020-12-12 11:31

    Should be possible with this sort of statement:

    INSERT INTO table_name (a, b) VALUES (1, 2) ON CONFLICT UPDATE SET a = 1, b = 2
    
    0 讨论(0)
  • 2020-12-12 11:32

    I could not find a SQLite query that would insert or update without causing unwanted changes to my foreign key, so instead I opted to insert first, ignoring conflicts if they occurred, and updating immediately afterwards, again ignoring conflicts.

    The insert and update methods are protected so external classes see and use the upsert method only. Keep in mind that this isn't a true upsert as if any of the MyEntity POJOS have null fields, they will overwrite what may currently be in the database. This is not a caveat for me, but it may be for your application.

    @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);
    }
    
    0 讨论(0)
  • 2020-12-12 11:34

    Perhaps you can make your BaseDao like this.

    secure the upsert operation with @Transaction, and try to update only if insertion is failed.

    @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);
            }
        }
    }
    
    0 讨论(0)
  • 2020-12-12 11:40

    For more elegant way to do that I would suggest two options:

    Checking for return value from insert operation with IGNORE as a OnConflictStrategy (if it equals to -1 then it means row wasn't inserted):

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

    Handling exception from insert operation with FAIL as a 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);
        }
    }
    
    0 讨论(0)
  • 2020-12-12 11:42

    Just an update for how to do this with Kotlin retaining data of the model (Maybe to use it in a counter as in example):

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

    You can also use @Transaction and database constructor variable for more complex transactions using database.openHelper.writableDatabase.execSQL("SQL STATEMENT")

    0 讨论(0)
提交回复
热议问题