preparedstatement能防sql注入因其将sql模板与参数物理隔离,数据库预编译固定语法树后仅把setxxx()传入的值当纯数据绑定,不参与解析;但仅限参数值,表名、列名等结构信息必须白名单校验。

Java 17 下必须用 PreparedStatement,不能拼接 SQL 字符串——这是唯一可靠、零配置的防御手段。
为什么 Java 17 仍要手动写 PreparedStatement?
Java 17 没有自动拦截字符串拼接 SQL 的机制,JDBC 规范也未强制驱动改写 Statement 行为。哪怕用了 Spring Boot 3.x + Jakarta EE 9+,只要代码里出现 "SELECT * FROM user WHERE id = '" + id + "'",就直接暴露在注入风险下。
常见错误现象:
- 用
String.format()或StringBuilder拼出 SQL 后再传给createStatement()—— 看似“没用 +”,实则等同于明文拼接 - 把
PreparedStatement当成“带问号的 Statement”用:写成"WHERE name = '?' "(引号包住问号),导致占位符失效,参数被当字面量处理 - 在
try-with-resources外提前 closeConnection,使PreparedStatement实际未执行或抛SQLException
setXxx() 方法选错会怎样?
Java 17 的 JDBC 驱动(如 MySQL Connector/J 8.3+、PostgreSQL JDBC 42.7+)对类型校验更严格,类型不匹配不再静默转换,而是直接报错或存入异常值。
使用场景和要点:
-
setString(1, "2024-01-01")传给DATE字段 → 可能解析失败,或被截断为1970-01-01(取决于数据库方言) - 字段是
BIGINT,Java 用Long接收,但调用setInt(1, id)→ 抛SQLException:“Data truncation” - 字段允许 NULL,却用
setInt(1, null)→NullPointerException;必须用setNull(1, Types.BIGINT) - 不确定字段类型时,查数据库 schema,而不是看 Java 变量类型(例如 DB 是
TIMESTAMP,就该用setTimestamp(),不是setString())
哪些地方 ? 占位符根本不能用?
PreparedStatement 的占位符只支持**运行时值**,不支持任何 SQL 结构片段。强行拼接会导致编译失败或绕过防护。
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
典型误用:
- 表名:
"SELECT * FROM ? WHERE id = ?"→ 报错 “Parameter index out of range” 或直接语法错误 - 列名:
"SELECT ?, name FROM user"→ 同样不被允许,数据库拒绝预编译 - 排序方向:
"ORDER BY create_time ?"(想传"DESC")→ 占位符无法替换关键字,必须白名单校验后字符串拼接 -
IN子句动态长度:"id IN (?)"只能绑一个值;多个需按实际个数生成对应数量的?,再循环setXxx()
这些位置若需动态,必须走白名单校验(比如枚举定义合法表名)、或用 ORM(如 MyBatis 的 <foreach></foreach> 或 JPA Criteria API)。
Java 17 下的兼容性与性能注意点
Java 17 默认使用 TLS 1.3、禁用 Applet,但 JDBC 行为本身未变。真正影响执行计划复用的是数据库服务端配置,而非 Java 版本。
关键事实:
- MySQL 默认仍为
useServerPrepStmts=false(客户端模拟预编译),执行计划不缓存;需显式加参数?useServerPrepStmts=true&cachePrepStmts=true - PostgreSQL JDBC 驱动默认启用服务端预编译,但首次执行仍需解析模板;后续相同 SQL 字符串(空格、大小写、换行全等)才复用执行计划
-
PreparedStatement对象本身不跨 Connection 复用;每次connection.prepareStatement(sql)都可能新建句柄,别试图全局缓存它 - Java 17 的
var关键字可用,但别写成var ps = conn.prepareStatement(...)—— 类型擦除后不利于静态分析工具识别资源泄漏
最易被忽略的一点:SQL 模板字符串中任意不可见字符(如全角空格、零宽空格)都会让数据库认为是“新语句”,导致预编译缓存失效——建议所有 SQL 字符串用 IDE 的“显示不可见字符”功能检查一遍。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










