preparedstatement预编译在preparestatement()调用时由数据库完成,包括语法解析、语义校验和执行计划生成;后续setxxx()仅传参复用计划,不重新编译,从而提升性能并防止sql注入。

PreparedStatement 在 Java 中实现 SQL 预编译,核心是把 SQL 模板(含占位符 ?)提前发送给数据库,由数据库完成语法解析、执行计划生成等编译工作;后续每次执行只需传入参数值,跳过重复编译,提升性能并防止 SQL 注入。
预编译发生在创建 PreparedStatement 时
调用 Connection.prepareStatement(String sql) 方法的那一刻,JDBC 驱动会将 SQL 字符串发给数据库。数据库收到后立即进行词法分析、语法检查、语义校验,并生成执行计划(如索引选择、连接策略),这个过程就是“预编译”。它不依赖参数值,只依赖 SQL 结构本身。
- 即使还没调用
execute()或executeUpdate(),预编译已经完成 - 若 SQL 有语法错误(如表名不存在、字段拼错),会在
prepareStatement()调用时就抛出SQLException - 不同数据库对“预编译”的实际支持程度略有差异(如 MySQL 默认开启 server-side prepare,PostgreSQL 通过
prepareThreshold控制是否启用)
参数绑定不触发重新编译
调用 setXxx(int parameterIndex, ...) 设置参数时,只是把值序列化后传给数据库,数据库直接复用之前生成的执行计划,无需再次解析 SQL 或重选执行路径。
- 例如:同一句
"SELECT * FROM user WHERE id = ?",无论传入1还是999,都走同一个预编译后的计划 - 这对批量操作尤其高效:循环中反复调用
setLong(1, id)+addBatch(),底层通常只做一次网络传输+一次计划复用 - 注意:如果参数类型与字段类型严重不匹配(如对 INT 字段 setString),可能触发隐式转换,极少数场景下影响执行计划选择,但不会导致重新编译 SQL 本身
驱动和数据库协同完成预编译
预编译不是纯客户端行为,而是 JDBC 驱动与数据库服务端配合的结果:
- MySQL Connector/J:默认开启 server-side prepare(
useServerPrepStmts=true),SQL 发送到 MySQL Server 后由其编译并分配一个 statement ID;关闭后则退化为客户端模拟(仅防注入,无真正计划复用) - PostgreSQL:JDBC 驱动在第 5 次执行相同 SQL 时自动触发
PREPARE命令(可配prepareThreshold修改阈值) - Oracle:通过
OraclePreparedStatement支持真正的绑定变量编译,执行时绑定参数即可
如何验证是否真正预编译
可通过数据库侧日志或监控确认:
- MySQL:开启
general_log,观察日志中是否出现Prepare和Execute分离的记录(而非全是Query) - PostgreSQL:查询
pg_prepared_statements视图,看是否有对应名称的 prepared statement 存在 - JDBC 层:部分驱动提供调试开关(如 HikariCP 的
logLevel=DEBUG)可打印底层 prepare 行为
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











