直接拼接字符串的Statement必然导致SQL注入,因其不隔离参数,用户输入如' OR '1'='1会原样拼入SQL执行;PreparedStatement需用?占位、按序setXxx()赋值、类型匹配,LIKE需参数值内加通配符,NULL须用setNull(),且必须try-with-resources确保close()。

为什么直接拼接字符串的 Statement 一定会导致SQL注入
因为 Statement 不做任何参数隔离,你传进去的字符串原样拼进SQL里。哪怕用户输入 ' OR '1'='1,最终执行的就是 SELECT * FROM user WHERE name = '' OR '1'='1'——整张表都被查出来了。这不是“可能出问题”,而是只要用了字符串拼接 + Statement,就等于把数据库钥匙交给了输入框。
替换为 PreparedStatement 的三步实操要点
不是简单把 Statement 换成 PreparedStatement 就完事。关键在占位符、设值顺序和类型匹配:
- SQL 中所有动态值必须用
?占位,不能出现在 SQL 字符串里(比如"WHERE id = " + id是错的,必须写成"WHERE id = ?") - 用
setXxx()方法按?出现顺序赋值,下标从 1 开始(ps.setInt(1, userId)),别用 0 或错位 - 类型要匹配:数值用
setInt()/setLong(),字符串用setString(),时间用setTimestamp();混用会导致隐式转换失败或精度丢失
PreparedStatement 对 LIKE 查询和 NULL 值的特殊处理
这两个场景最容易漏掉细节,直接照搬 setString() 会出错:
- LIKE 模糊查询要手动加通配符,但不能写在 SQL 字符串里(如
"WHERE name LIKE '%?%'"是错的),得在参数值里拼:ps.setString(1, "%" + keyword + "%") - NULL 值不能用
setString(1, null),会抛NullPointerException;必须用setNull(1, Types.VARCHAR),且第二个参数要和字段实际类型一致(比如整型字段用Types.INTEGER)
别忽略预编译缓存和连接生命周期
PreparedStatement 的安全性不依赖“是否预编译”,而依赖“参数与SQL分离”。但实际性能和兼容性受以下影响:
立即学习“Java免费学习笔记(深入)”;
- MySQL 默认开启预编译(
useServerPrepStmts=true),但低版本驱动对IN (?, ?, ?)这种变长列表支持差,建议用 MyBatis 等框架处理 -
PreparedStatement必须和Connection绑定,不能跨连接复用;用完必须close(),否则连接池里的连接可能被标记为“脏”并拒绝后续请求 - 某些旧数据库(如 Oracle 9i)对绑定变量个数有限制,超限会报
ORA-01000: maximum open cursors exceeded,这时候得检查是否没 close 或连接泄漏
最常被跳过的其实是异常路径下的 close()——try-with-resources 不是可选项,是必选项。


















