Z
ZHANK
编程在线教程Java 教程PreparedStatement 与事务
JDBC 与数据库

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 就够了。