3、SQLiteDatabase核心API:execSQL与rawQuery、insert/update/delete/query方法、事务处理beginTransaction、编译优化compileStatement

说实话,Android开发里跟SQLite打交道,绕不开的就是SQLiteDatabase这个类。它就像是你跟数据库之间的桥梁,所有增删改查都得通过它来干。今天我就把这几个核心API掰开揉碎了讲清楚,都是我在项目里实打实踩过的坑。

3.1 execSQL与rawQuery:最原始的SQL执行方式

先说说execSQL和rawQuery。这俩是最底层的API,说白了就是直接往数据库里扔SQL语句。

execSQL:只执行,不返回

execSQL用来执行那些不需要返回结果集的SQL,比如CREATE TABLE、INSERT、UPDATE、DELETE。它不返回任何数据,只告诉你执行成功还是失败。

// 创建表
db.execSQL("CREATE TABLE IF NOT EXISTS user (" +
        "id INTEGER PRIMARY KEY AUTOINCREMENT, " +
        "name TEXT NOT NULL, " +
        "age INTEGER)");

// 插入数据
db.execSQL("INSERT INTO user(name, age) VALUES('张三', 25)");

// 更新数据
db.execSQL("UPDATE user SET age = 26 WHERE name = '张三'");
注意:execSQL不能执行SELECT语句,否则会抛出SQLException。我曾经有个同事刚入行时拿execSQL查数据,debug了半天才发现问题。

rawQuery:查询并返回Cursor

rawQuery专门用来执行SELECT语句,返回一个Cursor对象。Cursor就像是一个游标,指向查询结果集的某一行。

Cursor cursor = db.rawQuery("SELECT * FROM user WHERE age > ?", new String[]{"20"});
while (cursor.moveToNext()) {
    int id = cursor.getInt(cursor.getColumnIndex("id"));
    String name = cursor.getString(cursor.getColumnIndex("name"));
    int age = cursor.getInt(cursor.getColumnIndex("age"));
    // 处理数据...
}
cursor.close(); // 别忘了关闭!
我的习惯:用rawQuery时,参数一定要用?占位符,别自己拼接字符串。为什么?防止SQL注入啊!我见过太多因为拼接字符串导致数据泄露的案例了。

3.2 insert/update/delete/query:封装好的便捷方法

说实话,execSQL和rawQuery虽然灵活,但写起来有点啰嗦。Google也想到了这点,所以提供了insert、update、delete、query这四个封装好的方法。我个人更推荐用这些,代码更清晰,也不容易出错。

insert:插入数据

ContentValues values = new ContentValues();
values.put("name", "李四");
values.put("age", 30);
long rowId = db.insert("user", null, values);
if (rowId != -1) {
    // 插入成功,rowId是新行的ID
}

这里有个小细节:第二个参数nullColumnHack。当values为空时,它会指定一个允许为NULL的列名,避免插入空行。不过我个人习惯是values不为空,所以直接传null。

update:更新数据

ContentValues values = new ContentValues();
values.put("age", 31);
int rowsAffected = db.update("user", values, "name = ?", new String[]{"李四"});
// rowsAffected表示受影响的行数

delete:删除数据

int rowsDeleted = db.delete("user", "age < ?", new String[]{"18"});
// 删除所有age小于18的用户

query:查询数据

Cursor cursor = db.query("user",           // 表名
        new String[]{"id", "name", "age"}, // 要查询的列
        "age > ?",                         // WHERE条件
        new String[]{"20"},                // WHERE参数
        null,                              // GROUP BY
        null,                              // HAVING
        "age DESC");                       // ORDER BY
核心要点:query方法的参数虽然多,但每个都有明确用途。我建议你记住这个顺序:表名 → 列名 → WHERE条件 → 参数 → GROUP BY → HAVING → ORDER BY。写多了自然就熟了。

3.3 事务处理:beginTransaction与endTransaction

事务处理这块,我得好好说说。你在项目里肯定会遇到需要同时执行多条SQL的场景,比如转账、批量导入数据。这时候事务就派上用场了。

为什么需要事务?

想象一下:你要给用户A扣100元,给用户B加100元。如果扣完A的钱后程序崩溃了,B没收到钱,那账就对不上了。事务能保证:要么全部成功,要么全部回滚。

基本用法

db.beginTransaction();
try {
    // 执行多条SQL
    db.execSQL("UPDATE account SET balance = balance - 100 WHERE name = 'A'");
    db.execSQL("UPDATE account SET balance = balance + 100 WHERE name = 'B'");
    
    db.setTransactionSuccessful(); // 标记事务成功
} catch (Exception e) {
    // 异常时自动回滚
} finally {
    db.endTransaction(); // 结束事务
}
避坑指南:我曾经犯过一个错误——忘记调用setTransactionSuccessful()。结果所有操作都回滚了,数据一条都没写进去。记住:beginTransaction后,一定要在try块里调用setTransactionSuccessful(),否则默认回滚。

批量插入的性能优化

如果你要插入1000条数据,逐条插入会非常慢。用事务包裹起来,速度能提升几十倍。

db.beginTransaction();
try {
    for (int i = 0; i < 1000; i++) {
        ContentValues values = new ContentValues();
        values.put("name", "用户" + i);
        values.put("age", i % 100);
        db.insert("user", null, values);
    }
    db.setTransactionSuccessful();
} finally {
    db.endTransaction();
}

3.4 编译优化:compileStatement

最后说说compileStatement。这玩意儿你可能用得不多,但在高频写入场景下,它简直是性能利器。

什么是compileStatement?

简单说,它就是预编译SQL语句。你把SQL模板编译一次,后面只需要绑定参数就能执行,省去了每次解析SQL的开销。

使用示例

String sql = "INSERT INTO user(name, age) VALUES(?, ?)";
SQLiteStatement statement = db.compileStatement(sql);

db.beginTransaction();
try {
    for (int i = 0; i < 10000; i++) {
        statement.clearBindings();
        statement.bindString(1, "用户" + i);  // 绑定第1个参数
        statement.bindLong(2, i % 100);       // 绑定第2个参数
        statement.executeInsert();            // 执行插入
    }
    db.setTransactionSuccessful();
} finally {
    db.endTransaction();
    statement.close(); // 别忘了释放
}
性能对比:我做过测试,插入10000条数据,用ContentValues+事务大约需要800ms,而用compileStatement+事务只需要200ms左右。差距就是这么明显。

compileStatement的常用方法

方法 说明
bindNull(int index) 绑定NULL值
bindLong(int index, long value) 绑定整型值
bindDouble(int index, double value) 绑定浮点值
bindString(int index, String value) 绑定字符串值
executeInsert() 执行INSERT,返回新行ID
executeUpdateDelete() 执行UPDATE或DELETE,返回受影响行数
simpleQueryForLong() 执行查询,返回long值
simpleQueryForString() 执行查询,返回String值
我的建议:如果你需要频繁执行同一条SQL(比如批量插入、批量更新),优先考虑compileStatement。但如果只是偶尔执行一两次,用ContentValues就足够了,别过度优化。

知识体系总览

下面这张图把今天讲的核心API串起来了,你可以对照着回顾一下。

SQLiteDatabase 核心API体系 SQLiteDatabase 原始SQL执行 execSQL rawQuery 封装便捷方法 insert update delete query 事务处理 beginTransaction setTransactionSuccessful endTransaction 编译优化 compileStatement + 绑定参数 核心原则:根据场景选择合适API,事务保证一致性,编译优化提升性能

嗯,以上就是SQLiteDatabase最核心的几个API。从最原始的execSQL/rawQuery,到封装好的insert/update/delete/query,再到事务处理和编译优化,每个都有自己最适合的场景。我个人建议:日常开发优先用封装方法,批量操作上事务,高频写入用compileStatement。这样写出来的代码既清晰又高效。


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