preparedstatement防sql注入靠的是sql模板与参数的物理分离,而非转义或过滤;数据库先预编译不含参数的语句,后续参数仅作为纯数据通过二进制协议传入,不参与sql解析。

PreparedStatement 防 SQL 注入,靠的不是“转义字符”或“过滤单引号”,而是把 SQL 结构和用户数据从物理层面彻底分开——数据库先编译好固定模板,后续只把输入当纯数据塞进去,不参与任何语法解析。
参数必须全部走 setXxx() 绑定
所有用户输入,不管来自表单、URL 还是 API 请求,都得通过 setString()、setInt()、setTimestamp() 等方法传入。不能漏掉任何一个,也不能用字符串拼接补上。
- ✅ 正确:
SELECT * FROM user WHERE name = ? AND age > ?,再调用ps.setString(1, name)和ps.setInt(2, age) - ❌ 错误:
"SELECT * FROM user WHERE name = '" + name + "' AND age > " + age,哪怕后面套了prepareStatement()也没用 - ⚠️ 特别注意空值:要用
setNull(1, Types.VARCHAR),不能对setInt()传null,否则直接抛NullPointerException
占位符 ? 只支持值,不支持结构
表名、列名、排序字段(如 ORDER BY 后的字段)、分组方向(ASC/DESC)这些都不能用 ?,因为 JDBC 规范不支持,数据库会报错或当成字面量处理。
- ❌ 危险写法:
"SELECT * FROM " + tableName + " WHERE id = ?"—— 表名一拼接,预编译就失效 - ✅ 安全做法:对这类动态结构做白名单校验,例如只允许
"name"、"email"、"created_at"出现在ORDER BY后 - ? 示例:
if (Arrays.asList("name", "email", "status").contains(sortField)) { sql += " ORDER BY " + sortField; }
IN 子句和批量操作要小心处理
WHERE id IN (?) 只能匹配一个值;多个值需要动态生成对应数量的 ?,但生成逻辑本身不能拼接用户输入。
- ✅ 推荐方式:限制最大个数(如 ≤ 100),用循环构建占位符串(如
"?, ?, ?"),再拼进 SQL;之后用addBatch()+ 多次setXxx()批量执行 - ❌ 避免:
"WHERE id IN (" + idsStr + ")",其中idsStr来自用户输入 - ? 小技巧:可预先定义几组固定长度(如 1/3/5/10 个 ?),根据实际数量选择,避免运行时拼接
复用 PreparedStatement 实例才能真正发挥效果
每次 conn.prepareStatement(sql) 都新建对象,等于只用了“半截功能”:既浪费资源,又可能掩盖安全问题。预编译的价值在于数据库缓存执行计划 + 客户端复用语句对象。
- ✅ 高频查询可封装为类成员变量(注意线程安全,建议配合
ThreadLocal或连接池作用域管理) - ✅ 批量插入/更新务必复用同一个
PreparedStatement,反复调用setXxx()→addBatch()→executeBatch() - ❌ 方法内临时创建、用完就 close,尤其在连接池环境下,容易触发资源告警
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











