PreparedStatement 与事务
使用预编译语句防注入、事务 ACID、回滚机制
PreparedStatement 与事务
SQL 注入是 Web 安全的第一大杀手,事务则是保证数据一致性的最后防线。这两件事,PreparedStatement 都能帮你搞定。
学完本章你将: 理解 SQL 注入原理与防御、事务 ACID 与回滚、隔离级别。
SQL 注入 —— 黑客最喜欢的漏洞
当你把用户输入直接拼接到 SQL 字符串里,用户就可以注入自己的 SQL 语句,获取、篡改甚至删除你的数据。
java
// ❌ 危险:字符串拼接 SQL
String userInput = request.getParameter("name"); // 用户输入
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(
"SELECT * FROM users WHERE name = '" + userInput + "'"
);
// 用户输入:' OR '1'='1' --
// 实际执行的 SQL:
// SELECT * FROM users WHERE name = '' OR '1'='1' --'
// ↑ 恒为真 ↑ 注释掉后面的
// 结果:返回所有用户数据!
java
// ✅ 安全:PreparedStatement,参数和 SQL 分离
PreparedStatement pstmt = conn.prepareStatement(
"SELECT * FROM users WHERE name = ?"
);
pstmt.setString(1, userInput); // 参数被安全转义,不会被当作 SQL 执行
PreparedStatement 还能防什么?
- 预编译 SQL,数据库只需解析一次(重复执行更快)
- 自动处理特殊字符(引号、反斜杠等)的转义
- 支持二进制大对象(BLOB)
事务 —— 要么全做,要么全不做
经典的银行转账场景:A 扣 1000 元,B 加 1000 元。如果扣了 A 的钱后系统崩溃,B 没收到——这 1000 元就蒸发了。事务保证这个流程要么全部完成,要么全部撤销。
java
Connection conn = DriverManager.getConnection(url, user, password);
conn.setAutoCommit(false); // 关闭自动提交(JDBC 默认每条 SQL 自动提交)
try {
// 操作 1:A 扣款
PreparedStatement deduct = conn.prepareStatement(
"UPDATE accounts SET balance = balance - ? WHERE id = ?"
);
deduct.setDouble(1, 1000);
deduct.setInt(2, 1);
deduct.executeUpdate();
// 操作 2:B 收款
PreparedStatement credit = conn.prepareStatement(
"UPDATE accounts SET balance = balance + ? WHERE id = ?"
);
credit.setDouble(1, 1000);
credit.setInt(2, 2);
credit.executeUpdate();
conn.commit(); // 提交:两笔操作同时生效
System.out.println("转账成功!");
} catch (SQLException e) {
conn.rollback(); // 回滚:任何一步失败,全部撤销
System.err.println("转账失败,已回滚:" + e.getMessage());
} finally {
conn.setAutoCommit(true); // 恢复默认
}
隔离级别 —— 并发事务互不干扰的程度
多个事务同时操作同一数据时,隔离级别决定了一个事务能否看到另一个事务未提交的修改。
| 级别 | 脏读 | 不可重复读 | 幻读 | 性能 |
|---|---|---|---|---|
| READ_UNCOMMITTED | ✅ 会 | ✅ 会 | ✅ 会 | 最快 |
| READ_COMMITTED | ❌ | ✅ 会 | ✅ 会 | ⭐ 推荐 |
| REPEATABLE_READ | ❌ | ❌ | ✅ 会 | MySQL 默认 |
| SERIALIZABLE | ❌ | ❌ | ❌ | 最慢 |
java
conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
💡 脏读:读到别人还没提交的数据(可能被回滚)。不可重复读:同一次事务里两次读同一行,结果不一样。幻读:同一次事务里两次查询,多出了几行。大多数场景用
READ_COMMITTED就够了。