直接拼接字符串的Statement必然不安全,因用户输入原样进入SQL导致注入;改用PreparedStatement需同时满足三条件:SQL用?占位、参数用setXxx()设置、调用方式不变。

为什么直接拼接字符串的Statement一定不安全
因为用户输入会原样进入SQL语句,数据库不区分“数据”和“代码”。比如用户名输入 ' OR '1'='1,拼成 "SELECT * FROM users WHERE name = '" + input + "'" 后,实际执行的是 SELECT * FROM users WHERE name = '' OR '1'='1'——整张表都被查出来了。这不是漏洞利用技巧,是字符串拼接的必然结果。
替换Statement时必须改三处地方
只把 Statement 换成 PreparedStatement 不够,漏掉任何一项都会白忙活:
- SQL 字符串里所有动态值必须改成
?占位符(不能用String.format或+拼接) - 所有参数必须通过
setXxx()方法设置(比如setString(1, username)),不能塞进 SQL 字符串里 - 原来用
executeQuery()或executeUpdate()的地方,调用方式不变,但对象已经是PreparedStatement
错误示例:String sql = "SELECT * FROM user WHERE id = " + id; → 即使后面用了 PreparedStatement,也完全无效。
setXxx() 方法选错会导致类型错误或截断
数据库字段类型和 setXxx() 方法要对得上,否则可能报错、丢数据,甚至绕过类型校验:
立即学习“Java免费学习笔记(深入)”;
- 整数字段(如
INT)优先用setInt(),别用setString()—— 虽然多数驱动能隐式转换,但 Oracle 等数据库可能报ORA-01722: invalid number - 时间字段(如
TIMESTAMP)用setTimestamp(),不是setString()或setDate();后者会丢失毫秒或时区信息 - 大文本(如
TEXT/CLOB)用setCharacterStream()或setString()(看长度),避免setBytes()误转码
特别注意:setNull(int parameterIndex, int sqlType) 必须传 JDBC 类型常量,比如 Types.VARCHAR,不能传 0 或 null。
批量操作和 NULL 值最容易漏处理
批量插入/更新时,很多人只改了单条逻辑,忘了循环里每次都要重设参数:
String sql = "INSERT INTO log (msg, level) VALUES (?, ?)";
try (PreparedStatement ps = conn.prepareStatement(sql)) {
for (LogEntry e : entries) {
ps.setString(1, e.getMsg()); // ✅ 每次都设
ps.setString(2, e.getLevel());
ps.addBatch();
}
ps.executeBatch();
}NULL 值不能直接 ps.setString(1, null) —— 这其实合法,但某些老驱动会出错;更稳妥的是显式调用 ps.setNull(1, Types.VARCHAR)。
真正麻烦的是嵌套查询或动态 WHERE 条件:参数个数不确定时,得先收集条件再拼 ?,不能硬编码索引。这种场景往往需要重构 SQL 组装逻辑,而不是简单替换 API。


















