mybatis底层通过routingstatementhandler路由至preparedstatementhandler,由其调用connection.preparestatement()创建预编译对象,并经typehandler体系安全绑定参数,实现防注入与性能优化。

MyBatis 底层对 PreparedStatement 的封装,不是简单调用 JDBC 接口,而是通过多层策略与代理机制,把预编译、参数绑定、结果映射等关键环节解耦并可扩展。核心在于:它用 StatementHandler 统一调度,用 RoutingStatementHandler 选型,最终由 PreparedStatementHandler 完成真正的预编译执行。
PreparedStatement 的创建时机和委托链
当调用 SqlSession.selectList() 时,MyBatis 会走到 Executor 的 query() 方法,再经由 Configuration.newStatementHandler() 创建一个 RoutingStatementHandler。这个 handler 是个门面,内部根据语句类型(STATEMENT/PREPARED/CALLABLE)动态选择实际处理器 —— 对应 #{} 参数的 SQL,默认走 PreparedStatementHandler。
-
RoutingStatementHandler不干活,只做路由;真实工作由PreparedStatementHandler承担 -
PreparedStatementHandler.prepare()负责调用Connection.prepareStatement(sql)获取原始PreparedStatement对象 - 该过程受
useServerPrepStmts等 JDBC 连接参数影响:设为true时走 MySQL 服务端预编译,否则由客户端模拟
参数是怎么安全塞进 PreparedStatement 的
MyBatis 不直接写 ps.setString(1, value),而是交由 TypeHandler 体系处理。每个参数类型(如 String、Integer、自定义对象)都有对应的 TypeHandler 实现,统一调用其 setParameter() 方法。
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- 例如
StringTypeHandler最终调用ps.setString(index, parameter) -
#{name}中的name会被解析为参数名或位置索引,再结合ParameterMapping映射到具体下标 - 所有
setParameter调用都发生在handler.parameterize(stmt)阶段,确保参数在执行前已绑定
预编译带来的实际好处不止是防注入
使用 PreparedStatement 在 MyBatis 中不只是为了防止 SQL 注入,还带来可量化的性能提升:
- MySQL 服务端会缓存编译后的执行计划(statement_id → plan),相同结构 SQL 多次执行无需重复解析
-
fetchSize可控:MyBatis 在prepareStatement后会设置stmt.setFetchSize(),控制每次从数据库拉取多少行,减少网络往返 - 批量操作更高效:MyBatis 的
addBatch()和executeBatch()就是基于PreparedStatement.addBatch()封装而来
为什么 ${} 不能防注入而 #{} 可以
根本区别在于介入阶段不同:
-
${}是字符串拼接,在 SQL 解析前就完成替换(相当于String.format),生成的 SQL 已含用户输入,交给PreparedStatement时已无“参数”概念 -
#{}是占位符,SQL 模板发给数据库的是带?的语句,参数值由PreparedStatement单独传输,MySQL 服务端严格区分“结构”和“数据”,天然隔离 - 即使你手动用
ps.setString(),只要走的是setParameter流程,就享受 JDBC 驱动层的转义与类型校验
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










