PreparedStatement 安全关键在于SQL结构固定、用户输入仅通过?占位并用setXxx()绑定;表名/列名等结构化参数须白名单校验;IN查询需动态生成占位符并批量处理;复用实例提升性能与安全性。

用 PreparedStatement 本身不能自动避免拼接隐患,关键在于所有用户可控内容都不能出现在 SQL 字符串里——只留固定结构,参数全交给 ? 占位,再用 setXxx() 绑定。
只让 ? 出现在值的位置
占位符 ? 是 PreparedStatement 的“安全边界”,它只接受数据值,不参与 SQL 解析。一旦你把变量拼进 SQL 字符串,预编译就形同虚设。
- ✅ 安全写法:"SELECT * FROM user WHERE name = ? AND age > ?" → 后续调用
setString(1, name)和setInt(2, minAge) - ❌ 危险写法:"SELECT * FROM " + tableName + " WHERE id = " + id → 即使后面用了 prepareStatement,也等于没用
- 数字、布尔、时间类型同样不能例外:必须用
setInt()、setBoolean()、setTimestamp(),不能靠字符串拼接或隐式转换
表名、列名、排序字段这些不能用 ?
? 不支持替换 SQL 结构,比如 "ORDER BY ?" 会报错或被当字面量处理,起不到作用。
- 正确做法是白名单校验:例如只允许
"name"、"created_time"、"status"进入 ORDER BY 子句 - 代码示例:
if ("name".equals(sortField)) sql += " ORDER BY name"; else if ("status".equals(sortField)) sql += " ORDER BY status"; - 动态表名同理:用枚举或配置项控制可选范围,禁止直接拼接用户输入
IN 查询和动态条件要小心处理
WHERE id IN (?) 只能匹配一个值,无法应对多个 ID 的场景;硬拼 "?, ?, ?" 又容易引入拼接风险。
立即学习“Java免费学习笔记(深入)”;
- 推荐方式:限制最大数量(如 ≤ 100),循环调用
addBatch()批量执行 - 多条件搜索(如姓名/邮箱/手机号任填其一):用 StringBuilder 拼 WHERE 片段,但只拼固定字段名和操作符,所有值仍走 ? + setXxx()
- 避免用
String.format()或正则替换构造 SQL 字符串后再传给 prepareStatement —— 那只是套壳,不防注入
复用 PreparedStatement 实例才能真正生效
每次 new 一个 PreparedStatement,相当于放弃数据库端执行计划缓存,也削弱客户端对象复用带来的安全性保障。
- 高频查询语句建议作为工具类成员变量持有(注意线程安全,可用 ThreadLocal 或连接池作用域管理)
- 批量插入/更新时:创建一次 PreparedStatement,反复 setXxx() → addBatch() → executeBatch()
- 在 HikariCP 等连接池中频繁新建丢弃,不仅性能差,还可能触发资源告警


















