preparedstatement 是防 sql 注入最直接可靠的方式,关键在于所有用户输入必须通过 setxxx() 绑定,sql 结构不可由用户控制,且需复用实例以兼顾安全与性能。

用 PreparedStatement 是 Java JDBC 防 SQL 注入最直接、最可靠的方式。关键不在于“用了没”,而在于“是否所有用户输入都严格走 setXxx() 绑定”,且 SQL 结构本身不能被用户控制。
所有变量必须用 ? 占位,且只通过 setXxx() 传值
数据库在 prepareStatement() 阶段就完成 SQL 解析和执行计划编译,? 占位符被固定为参数槽位;后续 setString(1, userInput)、setInt(2, id) 等操作,只是把值以二进制形式送入对应位置——值永远是数据,不会参与语法解析。
- ✅ 正确:
SELECT * FROM user WHERE name = ? AND status = ?→pstmt.setString(1, name); pstmt.setInt(2, status) - ❌ 错误:
"SELECT * FROM " + tableName + " WHERE id = " + id—— 任何字符串拼接,哪怕只加一个单引号或空格,都会让预编译失效 - 数字、时间、布尔类型也不能例外:必须用
setInt()、setTimestamp()、setBoolean(),避免隐式转换引发的边界绕过
表名、列名、ORDER BY、IN 子句数量等结构内容不能参数化
? 占位符只适用于“值”,不适用于 SQL 语法结构。这些动态部分必须通过白名单校验或枚举控制,绝不能由用户输入直接决定。
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- 排序字段示例:
if ("name".equals(sortField)) orderSql = "ORDER BY name"; else if ("age".equals(sortField)) orderSql = "ORDER BY age"; - IN 查询别拼
"?, ?, ?":应限制最大条目数(如 ≤ 100),再循环调用addBatch()批量执行 - 禁止用
String.format()、replaceAll()或正则替换构造 SQL 字符串后再传给prepareStatement()
复用 PreparedStatement 实例才能发挥性能与安全双重优势
PreparedStatement 的预编译价值分两层:数据库端缓存执行计划 + 客户端复用对象。如果每次查询都 new 一个新实例,等于只用了“半截功能”,还可能引发 Statement 泄漏。
- 批量插入/更新时:创建一次 PreparedStatement,反复调用
setXxx()→addBatch()→executeBatch() - 高频固定语句可封装为工具类的成员变量(注意线程安全,建议配合 ThreadLocal 或连接池 scope)
- 在 HikariCP 等连接池中,临时创建后立即丢弃 PreparedStatement,会增加 GC 压力,也削弱数据库端计划缓存效果
它不是万能的,但必须是第一道防线
PreparedStatement 能彻底阻断基于参数值的注入,但它不解决表名拼接、权限配置、敏感信息明文存储等问题。需配合输入格式校验(如邮箱用正则)、最小权限数据库账号、WAF 和定期安全扫描,形成纵深防御。
- 登录用户名可白名单限制为
[a-zA-Z0-9_]{3,20} - ID 类参数仍建议做类型强转(如
Long.parseLong()),但前提是已走 PreparedStatement —— 强转只是辅助,不是替代 - 密码永远不该明文比对,应使用 BCrypt 等哈希后查询,这点和防注入无关,但属基础安全常识
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










