8、更新操作详解:update() 方法实现、影响行数、条件更新

更新操作,说白了就是改数据。在 ContentProvider 里,update() 是四大操作里最容易踩坑的一个。我见过太多人把 delete 和 update 搞混,或者更新完发现影响行数不对,一脸懵。

今天咱们就把 update() 彻底聊透。从方法签名到实现细节,从条件更新到事务安全,一个都不放过。

8.1 update() 方法签名

先看接口长什么样:

@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
    // 你的实现
}

四个参数,一个返回值。我来拆开讲:

  • uri:操作路径,决定了你要更新哪张表
  • values:要改的新数据,键值对形式
  • selection:条件语句,类似 SQL 的 WHERE
  • selectionArgs:条件参数,防 SQL 注入用的
  • 返回值:影响的行数,int 类型

返回值很多人忽略。其实它很重要——客户端可以通过它判断更新是否成功。

核心要点:update() 返回的是实际被修改的行数,不是受影响的行数。如果某行数据没变化,它不会被计入。

8.2 基础实现:一个完整的 update()

直接上代码。这是我个人习惯的写法:

@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
    int count;
    String tableName;
    
    switch (uriMatcher.match(uri)) {
        case CODE_BOOK:
            tableName = TABLE_BOOKS;
            break;
        case CODE_BOOK_ID:
            tableName = TABLE_BOOKS;
            // 追加 ID 条件
            String id = uri.getLastPathSegment();
            if (selection != null && !selection.isEmpty()) {
                selection = selection + " AND " + COLUMN_ID + "=?";
            } else {
                selection = COLUMN_ID + "=?";
            }
            // 合并参数
            String[] newArgs;
            if (selectionArgs != null && selectionArgs.length > 0) {
                newArgs = new String[selectionArgs.length + 1];
                System.arraycopy(selectionArgs, 0, newArgs, 0, selectionArgs.length);
                newArgs[selectionArgs.length] = id;
            } else {
                newArgs = new String[]{id};
            }
            selectionArgs = newArgs;
            break;
        default:
            throw new IllegalArgumentException("Unknown URI: " + uri);
    }
    
    count = db.update(tableName, values, selection, selectionArgs);
    
    // 通知观察者
    if (count > 0) {
        getContext().getContentResolver().notifyChange(uri, null);
    }
    
    return count;
}

嗯,这里要注意:更新完一定要通知观察者。不然 UI 那边不会刷新,用户会以为操作没生效。

8.3 影响行数的秘密

为什么 update() 返回的行数有时候和预期不一样?我遇到过好几次这种问题。

举个例子:

ContentValues values = new ContentValues();
values.put("title", "Android 进阶");
values.put("price", 59.9);

int rows = getContentResolver().update(
    bookUri,
    values,
    "title = ?",
    new String[]{"Android 进阶"}
);

你猜 rows 是多少?

如果表里已经有一行 title 是 "Android 进阶",price 已经是 59.9,那 rows 就是 0。因为 SQLite 很聪明——数据没变,它就不算更新。

避坑指南:我曾经在批量更新时,用影响行数判断是否全部成功。结果因为部分数据没变化,行数对不上,导致业务逻辑出错。后来我改成用事务+计数的方式来解决。

8.4 条件更新的几种写法

条件更新是 update() 的精髓。我总结了几种常见场景:

8.4.1 精确匹配

// 更新指定 ID 的书
selection = "_id = ?";
selectionArgs = new String[]{"42"};

8.4.2 范围匹配

// 更新价格大于 50 的书
selection = "price > ?";
selectionArgs = new String[]{"50"};

8.4.3 多条件组合

// 更新某个作者的书,且价格低于 30
selection = "author = ? AND price < ?";
selectionArgs = new String[]{"张三", "30"};

8.4.4 模糊匹配

// 更新标题包含 "Android" 的书
selection = "title LIKE ?";
selectionArgs = new String[]{"%Android%"};

警告:永远不要拼接 SQL 字符串!用 selectionArgs 传参。我见过有人这么写:

// 危险!不要这样做!
selection = "title = '" + userInput + "'";

这等于把数据库大门敞开。用户输入 "'; DROP TABLE books;--" 你就哭了。

8.5 批量更新与事务

如果你要更新多行数据,建议用事务包裹。为什么?

你想想看,如果更新 100 行,第 50 行失败了,前面 49 行已经改了。数据就不一致了。

我的做法是这样:

@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
    SQLiteDatabase db = dbHelper.getWritableDatabase();
    int count = 0;
    
    db.beginTransaction();
    try {
        count = db.update(TABLE_BOOKS, values, selection, selectionArgs);
        db.setTransactionSuccessful();
    } finally {
        db.endTransaction();
    }
    
    if (count > 0) {
        getContext().getContentResolver().notifyChange(uri, null);
    }
    
    return count;
}

这样要么全部更新成功,要么全部回滚。数据一致性有保障。

8.6 更新操作的性能考量

更新操作看似简单,但性能问题不容忽视。我列几个要点:

场景 建议 原因
更新单行 用主键条件 主键有索引,速度最快
更新多行 用索引列做条件 避免全表扫描
频繁更新 批量提交,不要逐条 减少事务开销
更新大字段 考虑异步执行 避免阻塞 UI 线程

个人经验:我做过一个笔记 App,用户打字时实时保存。一开始每条笔记更新都直接调 update(),结果卡顿明显。后来改成 500ms 防抖+批量提交,流畅多了。

8.7 update() 的完整流程图

下面这张图把 update() 的完整流程画出来了。从客户端调用到数据库写入,每一步都清楚:

update() 完整执行流程 客户端调用 update() URI 匹配 → 确定表名 条件处理 + 参数合并 beginTransaction() SQLite 执行 UPDATE notifyChange() 返回影响行数 事务保证原子性,通知保证 UI 刷新

8.8 常见问题与解决方案

最后,我把自己踩过的坑整理一下:

  1. 更新后 UI 不刷新:忘了调 notifyChange()。记得在 count > 0 时通知。
  2. 影响行数为 0:数据没变化,或者条件写错了。先查一下数据是否存在。
  3. 更新了不该更新的行:条件太宽泛。比如忘了加 ID 条件,结果全表都改了。
  4. 并发更新数据错乱:没有用事务。多线程同时更新同一行,后写的覆盖先写的。
  5. SQL 注入风险:拼接字符串。用 selectionArgs 传参就安全了。

我的习惯:每次写 update(),我都会先写一个测试用例,用已知数据验证影响行数。确认无误后再上线。这招帮我避免了好几次生产事故。

update() 看似简单,但细节决定成败。条件写对了,事务用好了,通知到位了,你的 ContentProvider 才算真正靠谱。


公众号:蓝海资料掘金营,微信deep3321