Skip to content

SQL 如果靠字符串拼用户输入,结构就会被输入改掉。预编译把语句结构和参数分开:结构先固定,参数后绑,这是防注入的正路。

JDBC ​

java
String sql = "SELECT name, age FROM student WHERE id = ?";
try (Connection con = dataSource.getConnection();
     PreparedStatement ps = con.prepareStatement(sql)) {
    ps.setString(1, id);
    try (ResultSet rs = ps.executeQuery()) {
        while (rs.next()) {
            System.out.println(rs.getString("name") + " " + rs.getInt("age"));
        }
    }
}

? 是占位符。setString(1, id) 给第一个问号赋值,下标从 1 开始。不要再把 SQL 拼进 executeQuery。

拼接的反例:

java
// 不要这样
String sql = "SELECT * FROM student WHERE name = '" + userInput + "'";

userInput 里一旦出现 ' OR '1'='1,WHERE 的含义就变了。PreparedStatement 会把输入当数据,不当代码。

MyBatis 里的 #{} 和 ${} ​

#{} 走预编译,最终变成 JDBC 的 ?,会按类型设值,字符串会带引号。${} 是字符串替换,解析阶段就把变量写进 SQL,不带预编译。

sql
-- id = 1 时
SELECT * FROM student WHERE id = #{id}
-- 近似:WHERE id = ?

SELECT * FROM student WHERE id = ${id}
-- 近似:WHERE id = 1

能用 #{} 就用 #{}。ORDER BY 列名、动态表名这类不能参数化的片段,才不得不 ${},并且列名必须来自白名单,不能来自用户原文。

记住:#{} 是参数,${} 是插值。插值发生在编译前,注入也发生在编译期。

测试开发工程师 · 专注自动化与系统架构 | 邮箱: hansblog@atumsoul.win