最近我开发的一个功能需要从服务端获取json同步到本地的sqlite数据库,然后通知UI更新(Sqlbrite ftw)。这块的数据有一个字段叫isRead,当它为true的时候表示用户在UI上删除了它,app不再显示那条数据。
数据模型是这样的:
- 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;
-
- ...
-
- }
而数据库是这样的:
- 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集合应该是这样的:
- @Override
- public Observable<Alert> syncAlerts(final List<Alert> alerts) {
-
- Observable<Alert> observable = Observable.from(alerts)
- .doOnNext(new Action1<Alert>() {
- @Override
- public void call(Alert alert) {
-
- Cursor doesExistCursor = null;
- BriteDatabase.Transaction transaction = db.newTransaction();
- try {
- doesExistCursor = db.query(AlertQueries.byAlertIdQuery(alert.getAlertId()));
-
-
- 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是否已经存在于本地数据库中,如果不是,插入之。
但是这段代码有两个缺点。
-
我们跑了两次数据库(查询和插入)
-
如果Alert已经存在,我们不会更新各个字段,但是很可能上次查询的数据已经和之前不一样了。
ON CONFLICT REPLACE
解决这两个问题的方法就是在alertId上添加ON CONFLICT REPLACE
- 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来使用它
- 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));";
有点难懂,其实做了如下事情
-
我们使用INSERT OR REPLACE告诉数据库,如果插入有冲突则用新的值替换。
-
我们还确保了在同步的时候isRead字段不会被覆盖。我们是这样做的:查询字段当前值,如果不存在则设置一个默认的值0.
现在syncAlerts() 是这样的
- @Override
- public void syncAlerts(final List<Alert> 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 极大的简化我们的代码。
转自:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2016/0503/4202.html