9

Android的客房持久性库客客气气包括@Insert并为对象或集合工作@Update注解。然而,我有一个使用案例(包含模型的推送通知),因为数据可能存在或可能不存在于数据库中,所以需要UPSERT。安卓客房持久性库:的Upsert

SQLite没有UPSERT本身,和解决办法在此SO question描述。鉴于那里的解决方案,如何将它们应用于Room?

更具体地讲,我怎么能实现在房间的插入或更新,不会打破任何外键约束?使用带onConflict = REPLACE的insert会导致onDelete调用该行的任何外键。在我的情况onDelete导致级联,并重新插入行会导致其他表中的行与外键将其删除。这不是预期的行为。

回答

9

我无法找到一个SQLite查询,将插入或不会引起我的外键不必要的更改更新,所以不是我选择了先插入,忽视冲突,如果发生了他们,事后立即更新,再次忽略的冲突。

插入和更新方法受到保护,外部类看,并且只使用了更新插入方法。请记住,这是不是一个真正的UPSERT仿佛任何myEntity所的POJO具有空字段,他们将覆盖哪些当前可能在数据库中。这对我来说不是一个警告,但它可能适用于您的应用程序。

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

您可能想要使其效率更高并检查返回值。 -1表示任何类型的冲突。 – jcuypers

17

使用@Insert(onConflict = OnConflictStrategy.REPLACE)实施INSERT OR REPLACE,如图the accepted answer您链接到的问题。

+10

取而代之的是不要去,因为根据sqlite的文档来这里的路上:https://www.sqlite.org/lang_conflict.html更换会先删除该行,然后将其作为新行,导致标有onDelete的任何外键关系 - >该行级联删除。 这不是预期的行为。我只想更新行,如果它已经存在而不破坏任何外键关系。 –

+0

@Tunji_D:那么我建议你问一个单独的堆栈溢出的问题,在这里你解释*确切*您要执行的SQL。 – CommonsWare

+2

对于它的价值,我觉得这个问题说明了什么他想要做的,但我想,如果它包括了为什么“替换”不完成什么他尝试这样做,部分问题可以改进。 – AdamMc331

4

对于更优雅的方式做到这一点,我建议两个选择:与IGNORE

检查返回值从insert运营为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(models); 
    if (id == -1) { 
     update(models); 
    } 
} 

处理例外从insert操作与FAIL作为OnConflictStrategy

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

如何与科特林保留模型(也许用它在一个计数器作为例子)的数据做这只是一个更新:

//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语句”)更复杂的交易