PreparedStatement 防 SQL 注入的关键在于全程使用 ? 占位符、严格类型绑定及结构部分白名单控制;用户输入不得参与 SQL 语法构建,表名、列名等动态结构须经白名单校验,参数必须显式、精准类型绑定。

Java 中用 PreparedStatement 防 SQL 注入,关键不是“用了没”,而是“参数是否全程走 ? 占位、绑定是否严格类型匹配、结构部分是否白名单控制”。只要用户输入不参与 SQL 语法构建,数据库就根本不会把它当代码执行。
必须全程用 ? 占位,禁止任何字符串拼接
SQL 模板里不能出现变量名、用户输入或加号拼接。哪怕只拼一个表名、一个字段,整个防护就失效。
- ✅ 正确:
"SELECT * FROM users WHERE email = ? AND status = ?"→ 后续用setString(1, email)、setInt(2, status) - ❌ 错误:
"SELECT * FROM " + table + " WHERE id = '" + id + "'"→ 表名和值都拼接,完全绕过预编译 - ❌ 错误:
"WHERE name LIKE '%" + keyword + "%'"→ keyword 是"admin' -- "就直接注释掉后续条件
所有参数必须显式绑定,且类型精准对应
PreparedStatement 不接受“差不多”,类型错、漏设、传 null 都会出错或埋下隐患。
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- 数字字段(如 id、status)用
setInt()或setLong(),别用setString("123") - 时间字段(如 created_at)用
setTimestamp(),传new Date()或字符串都会失败或存错 - 空值必须用
setNull(index, Types.INTEGER),不能对setInt()传null - 避免用
setObject(),部分驱动对它的处理不一致,有潜在风险
动态结构(表名、列名、排序、IN 列表)必须白名单校验
? 只能代表“值”,不能代表“结构”。一旦需要动态决定查哪张表、按什么排序、IN 里几个值,就得靠规则兜底。
立即学习“Java免费学习笔记(深入)”;
- 排序字段只允许
"name"、"created_at"、"score"—— 先判断输入是否在列表中,再拼进 SQL -
ORDER BY ?是非法的,应写成"ORDER BY " + safeSortField + " " + safeOrder - IN 子句不能写成
WHERE id IN (?),而要按实际个数生成占位符,例如 3 个 ID 就是IN (?, ?, ?),再循环调用setLong() - 表名、列名绝对不可来自用户输入,除非经过白名单比对,否则直接拒绝请求
底层原理:先编译、后填参,数据走二进制通道
PreparedStatement 在 prepare 阶段就把 SQL 解析成执行计划,? 占位符只是预留的数据槽位;后续 setXXX() 传入的值走的是二进制参数通道,绝不会拼进 SQL 字符串里。数据库在语法树阶段就已固化结构,运行时只填值,不重解析。
- 恶意输入如
"admin' OR '1'='1"会被整体当作字符串值处理,自动加引号并转义 - 对比 Statement:拼接后整条 SQL 交由数据库解析,攻击者可篡改语义
- 这不是“大概率防住”,而是从 JDBC 协议层切断了数据被当作代码执行的路径

















