PreparedStatement 防 SQL 注入的核心是SQL结构与用户数据分离:SQL模板预编译,参数仅通过setXxx()传入;正确写法为全占位符、零字符串拼接,错误做法如拼接表名、列名或动态条件会导致失效。

PreparedStatement 能防 SQL 注入,核心在于把 SQL 结构和用户数据彻底分开:SQL 模板在 prepareStatement() 阶段就由数据库编译好,后续所有输入值都只通过 setXxx() 方法传入,数据库不会把它们当 SQL 代码解析。
正确写法:参数全走 setXxx(),SQL 字符串零拼接
✅ 安全示例:
- SQL 字符串固定,只含
?占位符:"SELECT * FROM user WHERE name = ? AND status = ?" - 参数严格用
setString(1, name)、setInt(2, status)绑定 - 即使
name是"admin' OR '1'='1",最终执行的也是:WHERE name = 'admin'' OR ''1''=''1'(单引号被转义,整个值被当作字符串字面量)
常见踩坑点:这些操作会让 PreparedStatement 失效
❌ 半截子预编译(最常犯):
在调试 Java 后端 API 故障时使用此技能,尤其适用于以下任务:分析日志、跨 Controller、Service 和 Mapper 层追踪请求、定位异常根因、检查 SQL 执行或参数流转,以及生成简洁的诊断结论与修复方案。
-
"SELECT * FROM user WHERE id = " + userId + " AND name = ?"——userId拼在 SQL 里,? 白搭 -
"SELECT * FROM " + tableName + " WHERE id = ?"—— 表名不能用?,必须白名单校验后拼接 -
"ORDER BY " + sortField + " " + sortOrder—— 列名和 ASC/DESC 都需白名单控制
动态条件与 IN 子句的处理方式
⚠️ IN (?, ?, ?) 不支持自动展开,不能写成 WHERE id IN (?)(只能匹配一个值):
- 固定个数:手动拼占位符,如
"WHERE id IN (?, ?, ?)",再用ps.setInt(1, a); ps.setInt(2, b); ... - 不定个数:用
StringBuilder动态拼 WHERE 片段(仅限字段名/操作符等静态部分),参数仍走setXxx() - IN 条件值本身来自用户?必须先校验是否在允许范围内(如 ID 列表只接受数字且长度≤100)
类型匹配与空值处理要严谨
? 类型错不仅报错,还可能引发隐式转换或性能问题:
- 数据库字段是
BIGINT,Java 用Long存 ID → 必须用setLong(),不用setInt() - 时间字段是
TIMESTAMP→ 用setTimestamp(),别用setString("2024-01-01") - 参数可能为 null → 用
setNull(index, Types.VARCHAR),不能对setInt()传null(会 NPE)
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










