在安卓中使用ON CONFLICT REPLACE同步数据到SQLITE

最近我开发的一个功能需要从服务端获取json同步到本地的sqlite数据库,然后通知UI更新(Sqlbrite ftw)。这块的数据有一个字段叫isRead,当它为true的时候表示用户在UI上删除了它,app不再显示那条数据。
数据模型是这样的:

1 2 3 4 5 6 7 8 9 10 11 public class Alert { private final String alertId; private final String alertTitle; private final String alertText; private final String alertActionText; private final String alertActionUrl; private final Boolean isRead; ... }
而数据库是这样的:

1 2 3 4 5 6 7 8 private static final String TABLE_CREATE_ALERTS = "CREATE TABLE " + ALERTS + "(" + ALERT_ID + " text UNIQUEE, " + ALERT_TITLE + " text, " + ALERT_TEXT + " text, " + ALERT_ACTION_TEXT + " text, " + ALERT_ACTION_URL + " text, " + ALERT_IS_READ + " integer default 0); " ;
当我们相服务器发起请求之后,同步返回的Alert集合应该是这样的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 @Override public Observable syncAlerts(final List alerts) { Observable observable = Observable.from(alerts) .doOnNext( new Action1() { @Override public void call(Alert alert) { Cursor doesExistCursor = null ; BriteDatabase.Transaction transaction = db.newTransaction(); try { doesExistCursor = db.query(AlertQueries.byAlertIdQuery(alert.getAlertId())); //then the row exists and we shouldn't insert if (doesExistCursor != null && doesExistCursor.moveToNext()) { return ; } ContentValues values = new AlertsContentValuesBuilder() .withAlertId(alert.getAlertId()) .withAlertTitle(alert.getAlertTitle()) .withAlertText(alert.getAlertText()) .withActionText(alert.getActionText()) .withActionUrl(alert.getActionUrl()) .build(); db.insert(MyOpenHelper.TABLE_ALERTS, values, SQLiteDatabase.CONFLICT_IGNORE); transaction.markSuccessful(); } finally { transaction.end(); if (doesExistCursor != null ) { doesExistCursor.close(); } } } }); return observable; }

大体来说,这段代码查看了该Alert是否已经存在于本地数据库中,如果不是,插入之。
【在安卓中使用ON CONFLICT REPLACE同步数据到SQLITE】 但是这段代码有两个缺点。
  1. 我们跑了两次数据库(查询和插入)
  2. 如果Alert已经存在,我们不会更新各个字段,但是很可能上次查询的数据已经和之前不一样了。
ON CONFLICT REPLACE 解决这两个问题的方法就是在alertId上添加ON CONFLICT REPLACE

1 2 3 4 5 6 7 8 private static final String TABLE_CREATE_ALERTS = "CREATE TABLE " + ALERTS + "(" + ALERT_ID + " text UNIQUE ON CONFLICT REPLACE, " + ALERT_TITLE + " text, " + ALERT_TEXT + " text, " + ALERT_ACTION_TEXT + " text, " + ALERT_ACTION_URL + " text, " + ALERT_IS_READ + " integer default 0); " ;
然后创建一个query来使用它

1 2 3 4 5 6 7 8 9 10 11 private static final String ALERT_INSERT_OR_REPLACE = "INSERT OR REPLACE INTO " + MyOpenHelper.TABLE_ALERTS + " ( " + MyOpenHelper.ALERT_ID + " , " + MyOpenHelper.ALERT_TITLE + " , " + MyOpenHelper.ALERT_TEXT + " , " + MyOpenHelper.ALERT_ACTION_TEXT + " , " + MyOpenHelper.ALERT_ACTION_URL + " , " + MyOpenHelper.ALERT_IS_READ + ") VALUES (?, ?, ?, ?, ?, COALESCE((SELECT " + MyOpenHelper.ALERT_IS_READ + " FROM " + MyOpenHelper.TABLE_ALERTS + " WHERE " + MyOpenHelper.ALERT_ID + " = ?), 0)); " ;
有点难懂,其实做了如下事情

  1. 我们使用INSERT OR REPLACE告诉数据库,如果插入有冲突则用新的值替换。
  2. 我们还确保了在同步的时候isRead字段不会被覆盖。我们是这样做的:查询字段当前值,如果不存在则设置一个默认的值0.
现在syncAlerts() 是这样的

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @Override public void syncAlerts(final List alerts) { for (Alert alert: alerts) { BriteDatabase.Transaction transaction = db.newTransaction(); try { db.executeAndTrigger(MyOpenHelper.TABLE_ALERTS, ALERT_INSERT_OR_REPLACE, alert.getAlertId(), alert.getAlertTitle(), alert.getAlertText(), alert.getActionText(), alert.getActionUrl(), alert.getAlertId()); transaction.markSuccessful(); } catch (Exception e) { Logger.errorNotify(e); } finally { transaction.end(); } } }
最后要注意的就是你该调用 BriteDatabase.executeAndTrigger(...)而不是BriteDatabase.execute(...)。

就这样稍微写了个复杂点的插入语句,我们就能利用Sqlite的INSERT OR REPLACE INTO 极大的简化我们的代码。

    推荐阅读