PreparedStatement仅在参数通过setXxx()绑定且SQL结构不拼接用户输入时才防注入;其性能优势依赖语句复用而非每次新建对象。

PreparedStatement 能防 SQL 注入,但只在正确使用时生效;它也能提升执行效率,但前提是复用语句对象,而非每次 new 一个。
为什么 PreparedStatement 不等于自动防注入
很多人以为只要用了 PreparedStatement 就安全了,其实不是。关键在于:SQL 模板里不能拼接用户输入的任意字符串。一旦出现类似 "SELECT * FROM " + tableName + " WHERE id = ?" 这种写法,tableName 若来自 HTTP 参数且未校验,PreparedStatement 完全失效——因为表名根本不能用 ? 占位。
常见错误包括:
- 手动拼接 SQL 字符串后再传给
prepareStatement()(比如用String.format()或+) - 用
setObject()传入未经清洗的 Map/List,而 JDBC 驱动对复杂对象的序列化逻辑存在风险 - 动态生成
IN (?, ?, ?)占位符时,靠字符串拼接构造 SQL,而不是用白名单控制字段或枚举固定长度
怎么写才真正防注入:参数必须走 setXxx() 绑定
核心是让数据库把输入当「数据」而非「代码」处理。这要求所有用户可控内容都通过 setString()、setInt()、setTimestamp() 等方法绑定,且占位符数量与调用次数严格一致。
例如登录验证:
String sql = "SELECT id, username FROM users WHERE username = ? AND status = ? AND created_at > ?"; PreparedStatement ps = conn.prepareStatement(sql); ps.setString(1, userInputName); // 自动处理单引号、反斜杠等 ps.setInt(2, 1); // 类型强约束,传字符串会报错 ps.setTimestamp(3, cutoffTime); // 时间类型不匹配直接抛异常,不会隐式转换
此时即使 userInputName 是 "admin' OR '1'='1",最终执行的仍是:
SELECT id, username FROM users WHERE username = 'admin'' OR ''1''=''1' AND status = 1 AND created_at > '2026-04-01 00:00:00'
注意:单引号被自动转义为两个单引号(取决于驱动),且整个值被包裹在引号内,无法跳出字符串上下文。
怎么提升执行效率:复用 PreparedStatement 实例
PreparedStatement 的性能优势来自两方面:数据库端的执行计划缓存 + 客户端的语句对象复用。但很多人忽略了后者——如果每次查询都 conn.prepareStatement(sql) 新建对象,就浪费了预编译开销,还可能触发连接池中 Statement 泄漏。
推荐做法:
- 在循环批量插入时,用
addBatch()+executeBatch(),别反复prepare - 在 DAO 层将常用查询封装为成员变量(如 Spring 中用
@PostConstruct初始化),避免每次方法调用都创建 - 确认数据库是否开启预编译缓存(MySQL 默认关闭
useServerPrepStmts=true,需显式配置) - 避免在事务中跨多个
Connection复用同一个PreparedStatement(会报 closed exception)
哪些地方 PreparedStatement 压根不管用
它只保护「参数值」,不保护「SQL 结构」。以下场景必须配合其他手段:
-
ORDER BY后的字段名(如"ORDER BY " + sortField)→ 必须白名单校验,或映射为枚举 - 动态表名/列名/UNION 查询分支→ 只能靠服务端硬编码枚举,或正则限制为
[a-zA-Z0-9_]+再查元数据表确认存在 -
LIKE模糊查询中的通配符(%和_)→ 需要调用setString()前手动转义,比如value.replace("\", "\\").replace("%", "\%").replace("_", "\_"),再加ESCAPE '\'
最易被忽略的是:很多项目把 PreparedStatement 当成银弹,却在日志打印、权限校验、分库分表路由等环节重新拼接 SQL——这些地方的漏洞,和 DAO 层用不用 PreparedStatement 完全无关。

















