MyBatis StatementHandler 深度解析:数据库交互的核心执行者 StatementHandler 是 MyBatis 中直接与数据库交互的核心接口 ,负责管理 JDBC 的 Statement 对象(Statement/PreparedStatement/CallableStatement),协调 ParameterHandler(参数设置)和 ResultSetHandler(结果映射),完成 “SQL 预编译→参数绑定→SQL 执行→结果处理” 的全流程。从 “接口定位→架构设计→核心实现→调用链路” 四个维度,彻底拆解 StatementHandler 的工作机制,并揭示其背后的设计模式。
StatementHandler 核心定位与接口规范 核心职责 StatementHandler 是 MyBatis 与数据库交互的 “最后一公里”,核心职责可概括为 5 点:
创建 Statement 对象 :根据 SQL 类型(无参数 / 预编译 / 存储过程)创建对应的 JDBC Statement;
SQL 预编译 :对预编译 SQL(PreparedStatement)进行编译,提升执行效率;
参数绑定 :委托 ParameterHandler 为 SQL 占位符(?)设置参数;
SQL 执行 :执行 select/insert/update/delete 及批量操作;
结果转发 :将执行结果(ResultSet)委托 ResultSetHandler 映射为 Java 对象。
接口定义:核心方法规范 StatementHandler 接口定义了数据库交互的标准方法,所有实现类需遵循该规范:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public interface StatementHandler { Statement prepare (Connection connection, Integer transactionTimeout) throws SQLException; void parameterize (Statement statement) throws SQLException; void batch (Statement statement) throws SQLException; int update (Statement statement) throws SQLException; <E> List<E> query (Statement statement, ResultHandler resultHandler) throws SQLException; <E> Cursor<E> queryCursor (Statement statement) throws SQLException; BoundSql getBoundSql () ; ParameterHandler getParameterHandler () ; }
架构设计:接口 + 抽象基类 + 具体实现(模板方法 + 策略模式) MyBatis 对 StatementHandler 的实现采用 “接口定义规范 + 抽象基类封装通用逻辑 + 具体类实现差异化功能” 的架构,并通过 模板方法模式 (抽象基类)和 策略模式 (路由类)降低耦合,提升扩展性。
架构层级图
graph TD
A[StatementHandler 接口] --> B[BaseStatementHandler 抽象基类]
B --> C[SimpleStatementHandler 无参数执行器]
B --> D[PreparedStatementHandler 预编译执行器]
B --> E[CallableStatementHandler 存储过程执行器]
A --> F[RoutingStatementHandler 路由执行器 策略模式]
F --> C & D & E[根据 statementType 选择 delegate]
StatementHandler 接口BaseStatementHandler 抽象基类SimpleStatementHandler 无参数执行器PreparedStatementHandler 预编译执行器根据 statementType 选择 delegateRoutingStatementHandler 路由执行器(策略模式)
设计模式解析:
模板方法模式 :BaseStatementHandler 实现 prepare() 方法的通用框架(如设置超时、fetchSize),将差异化逻辑(创建 Statement)抽象为 instantiateStatement() 由子类实现;
策略模式 :RoutingStatementHandler 作为 “策略路由”,根据 MappedStatement 的 statementType 选择对应的 delegate(Simple/Prepared/Callable),上层无需关心具体实现。
核心实现类解析 1. BaseStatementHandler:抽象基类(模板方法的核心) BaseStatementHandler 是所有具体执行器的父类,封装了 通用逻辑 (如参数 / 结果处理器创建、超时设置、Statement 关闭),暴露 instantiateStatement() 抽象方法由子类实现差异化的 Statement 创建逻辑。
核心源码与逻辑拆解 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 public abstract class BaseStatementHandler implements StatementHandler { protected final Configuration configuration; protected final ParameterHandler parameterHandler; protected final ResultSetHandler resultSetHandler; protected final Executor executor; protected final MappedStatement mappedStatement; protected final RowBounds rowBounds; protected final BoundSql boundSql; protected BaseStatementHandler (Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { this .configuration = ms.getConfiguration(); this .executor = executor; this .mappedStatement = ms; this .rowBounds = rowBounds; this .boundSql = boundSql; this .parameterHandler = configuration.newParameterHandler(ms, parameter, boundSql); this .resultSetHandler = configuration.newResultSetHandler(executor, ms, rowBounds, parameterHandler, resultHandler, boundSql); if (boundSql == null ) { generateKeys(parameter); this .boundSql = ms.getBoundSql(parameter); } } @Override public Statement prepare (Connection connection, Integer transactionTimeout) throws SQLException { ErrorContext.instance().sql(boundSql.getSql()); Statement statement = null ; try { statement = instantiateStatement(connection); setStatementTimeout(statement, transactionTimeout); setFetchSize(statement); return statement; } catch (SQLException e) { closeStatement(statement); throw e; } } protected abstract Statement instantiateStatement (Connection connection) throws SQLException; protected void setStatementTimeout (Statement stmt, Integer transactionTimeout) throws SQLException { Integer queryTimeout = mappedStatement.getTimeout() != null ? mappedStatement.getTimeout() : configuration.getDefaultStatementTimeout(); if (queryTimeout != null ) { stmt.setQueryTimeout(queryTimeout); } StatementUtil.applyTransactionTimeout(stmt, queryTimeout, transactionTimeout); } protected void setFetchSize (Statement stmt) throws SQLException { Integer fetchSize = mappedStatement.getFetchSize() != null ? mappedStatement.getFetchSize() : configuration.getDefaultFetchSize(); if (fetchSize != null ) { stmt.setFetchSize(fetchSize); } } protected void closeStatement (Statement statement) { if (statement != null ) { try { statement.close(); } catch (SQLException e) { } } } protected void generateKeys (Object parameter) { KeyGenerator keyGenerator = mappedStatement.getKeyGenerator(); keyGenerator.processBefore(executor, mappedStatement, null , parameter); } @Override public BoundSql getBoundSql () { return boundSql; } @Override public ParameterHandler getParameterHandler () { return parameterHandler; } }
核心价值:
解耦通用与差异化逻辑 :将超时设置、fetchSize 配置、Statement 关闭等通用逻辑封装,子类仅需关注 “如何创建 Statement”;
依赖注入 :在构造函数中创建 ParameterHandler 和 ResultSetHandler,建立 StatementHandler 与其他组件的协作关系;
主键预处理 :支持 SQL 执行前生成自增主键(如 useGeneratedKeys=true),确保主键能正确设置到实体对象。
2. 具体执行器:差异化实现(对应 JDBC 三种 Statement) MyBatis 提供三个具体执行器,分别对应 JDBC 的三种 Statement 类型,覆盖不同 SQL 场景。
(1)SimpleStatementHandler:无参数 SQL 执行器 对应 JDBC 的 Statement,不支持参数占位符(?) ,适合执行无参数的 SQL(如 SELECT * FROM user),parameterize() 方法为空实现。
核心源码解析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 public class SimpleStatementHandler extends BaseStatementHandler { public SimpleStatementHandler (Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { super (executor, ms, parameter, rowBounds, resultHandler, boundSql); } @Override public int update (Statement statement) throws SQLException { String sql = boundSql.getSql(); Object parameterObject = boundSql.getParameterObject(); KeyGenerator keyGenerator = mappedStatement.getKeyGenerator(); int rows; if (keyGenerator instanceof Jdbc3KeyGenerator) { statement.execute(sql, Statement.RETURN_GENERATED_KEYS); rows = statement.getUpdateCount(); keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject); } else if (keyGenerator instanceof SelectKeyGenerator) { statement.execute(sql); rows = statement.getUpdateCount(); keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject); } else { statement.execute(sql); rows = statement.getUpdateCount(); } return rows; } @Override public <E> List<E> query (Statement statement, ResultHandler resultHandler) throws SQLException { String sql = boundSql.getSql(); statement.execute(sql); return resultSetHandler.handleResultSets(statement); } @Override public void batch (Statement statement) throws SQLException { String sql = boundSql.getSql(); statement.addBatch(sql); } @Override protected Statement instantiateStatement (Connection connection) throws SQLException { if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) { return connection.createStatement(); } else { return connection.createStatement( mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY ); } } @Override public void parameterize (Statement statement) { } }
特点与适用场景
优点
缺点
适用场景
无需参数绑定,执行逻辑简单
不支持参数,SQL 需硬编码(如 WHERE id=1),易引发 SQL 注入
无参数的静态 SQL(如查询系统配置)
无预编译开销(适合一次性执行的简单 SQL)
不支持批量参数化(批量执行需重复拼接 SQL)
简单查询 / 测试场景
(2)PreparedStatementHandler:预编译 SQL 执行器 对应 JDBC 的 PreparedStatement,支持参数占位符(?) ,是 MyBatis 最常用的执行器(默认)。核心优势:预编译 SQL 可复用、防 SQL 注入、支持参数绑定。
核心源码解析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 public class PreparedStatementHandler extends BaseStatementHandler { public PreparedStatementHandler (Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { super (executor, ms, parameter, rowBounds, resultHandler, boundSql); } @Override public int update (Statement statement) throws SQLException { PreparedStatement ps = (Statement) statement; ps.execute(); int rows = ps.getUpdateCount(); Object parameterObject = boundSql.getParameterObject(); mappedStatement.getKeyGenerator().processAfter(executor, mappedStatement, ps, parameterObject); return rows; } @Override public <E> List<E> query (Statement statement, ResultHandler resultHandler) throws SQLException { PreparedStatement ps = (PreparedStatement) statement; ps.execute(); return resultSetHandler.handleResultSets(ps); } @Override public void batch (Statement statement) throws SQLException { PreparedStatement ps = (PreparedStatement) statement; ps.addBatch(); } @Override protected Statement instantiateStatement (Connection connection) throws SQLException { String sql = boundSql.getSql(); KeyGenerator keyGenerator = mappedStatement.getKeyGenerator(); if (keyGenerator instanceof Jdbc3KeyGenerator) { String[] keyColumnNames = mappedStatement.getKeyColumns(); return keyColumnNames == null ? connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS) : connection.prepareStatement(sql, keyColumnNames); } else if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) { return connection.prepareStatement(sql); } else { return connection.prepareStatement( sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY ); } } @Override public void parameterize (Statement statement) throws SQLException { parameterHandler.setParameters((PreparedStatement) statement); } }
核心优势:
预编译复用 :相同 SQL 只需预编译一次,后续执行仅需绑定参数,提升性能;
防 SQL 注入 :参数通过 PreparedStatement.setXxx() 传递,避免 SQL 拼接导致的注入风险;
参数灵活 :支持复杂参数类型(如实体、Map、数组),通过 ParameterHandler 自动转换。
适用场景:
几乎所有带参数的 SQL(如 SELECT * FROM user WHERE id=?);
高频重复执行的 SQL(如商品详情查询);
批量操作(配合 BatchExecutor 批量提交)。
(3)CallableStatementHandler:存储过程执行器 对应 JDBC 的 CallableStatement,专门用于调用数据库存储过程 ,支持 IN(输入参数)、OUT(输出参数)、INOUT(输入输出参数)类型,需手动注册输出参数。
核心源码解析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 public class CallableStatementHandler extends BaseStatementHandler { public CallableStatementHandler (Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { super (executor, ms, parameter, rowBounds, resultHandler, boundSql); } @Override public int update (Statement statement) throws SQLException { CallableStatement cs = (CallableStatement) statement; cs.execute(); int rows = cs.getUpdateCount(); Object parameterObject = boundSql.getParameterObject(); mappedStatement.getKeyGenerator().processAfter(executor, mappedStatement, cs, parameterObject); resultSetHandler.handleOutputParameters(cs); return rows; } @Override public <E> List<E> query (Statement statement, ResultHandler resultHandler) throws SQLException { CallableStatement cs = (CallableStatement) statement; cs.execute(); List<E> resultList = resultSetHandler.handleResultSets(cs); resultSetHandler.handleOutputParameters(cs); return resultList; } @Override protected Statement instantiateStatement (Connection connection) throws SQLException { String sql = boundSql.getSql(); return mappedStatement.getResultSetType() == ResultSetType.DEFAULT ? connection.prepareCall(sql) : connection.prepareCall( sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY ); } @Override public void parameterize (Statement statement) throws SQLException { CallableStatement cs = (CallableStatement) statement; registerOutputParameters(cs); parameterHandler.setParameters(cs); } private void registerOutputParameters (CallableStatement cs) throws SQLException { List<ParameterMapping> parameterMappings = boundSql.getParameterMappings(); for (int i = 0 ; i < parameterMappings.size(); i++) { ParameterMapping pm = parameterMappings.get(i); if (pm.getMode() == ParameterMode.OUT || pm.getMode() == ParameterMode.INOUT) { JdbcType jdbcType = pm.getJdbcType(); if (jdbcType == null ) { throw new ExecutorException ("OUT 参数必须指定 JdbcType:" + pm.getProperty()); } if (pm.getNumericScale() != null && (jdbcType == JdbcType.NUMERIC || jdbcType == JdbcType.DECIMAL)) { cs.registerOutParameter(i + 1 , jdbcType.TYPE_CODE, pm.getNumericScale()); } else if (pm.getJdbcTypeName() != null ) { cs.registerOutParameter(i + 1 , jdbcType.TYPE_CODE, pm.getJdbcTypeName()); } else { cs.registerOutParameter(i + 1 , jdbcType.TYPE_CODE); } } } } }
关键细节:
存储过程语法 :SQL 需用 {CALL 存储过程名(?, ?)} 格式(如 {CALL get_user(#{id,mode=IN}, #{name,mode=OUT,jdbcType=VARCHAR})});
输出参数注册 :必须通过 registerOutParameter() 告知 JDBC 输出参数的索引和类型,否则无法获取输出值;
结果处理 :存储过程可能返回多个结果集(ResultSet)和输出参数,需通过 handleOutputParameters() 单独处理输出参数。
适用场景:
调用数据库存储过程(如复杂的业务逻辑封装在存储过程中);
需要获取存储过程输出参数的场景(如计算结果、状态码)。
3. RoutingStatementHandler:执行器路由(策略模式) RoutingStatementHandler 本身不直接执行 SQL,而是根据 MappedStatement 的 statementType 属性,动态选择对应的 delegate 执行器 (Simple/Prepared/Callable),是策略模式的典型应用。
核心源码解析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 public class RoutingStatementHandler implements StatementHandler { private final StatementHandler delegate; public RoutingStatementHandler (Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { switch (ms.getStatementType()) { case STATEMENT: delegate = new SimpleStatementHandler (executor, ms, parameter, rowBounds, resultHandler, boundSql); break ; case PREPARED: delegate = new PreparedStatementHandler (executor, ms, parameter, rowBounds, resultHandler, boundSql); break ; case CALLABLE: delegate = new CallableStatementHandler (executor, ms, parameter, rowBounds, resultHandler, boundSql); break ; default : throw new ExecutorException ("未知 statementType:" + ms.getStatementType()); } } @Override public Statement prepare (Connection connection, Integer transactionTimeout) throws SQLException { return delegate.prepare(connection, transactionTimeout); } @Override public void parameterize (Statement statement) throws SQLException { delegate.parameterize(statement); } @Override public int update (Statement statement) throws SQLException { return delegate.update(statement); } }
核心价值:
解耦选择逻辑 :上层(如 Executor)无需关心具体执行器的创建,只需通过 statementType 配置即可切换;
简化扩展 :若新增执行器(如自定义 Statement 类型),只需在 switch 中添加分支,符合开闭原则。
StatementHandler 调用链路:与 Executor 的协作 StatementHandler 并非独立工作,而是由 Executor(执行器)调度 ,形成 “Executor→StatementHandler→ParameterHandler/ResultSetHandler” 的完整调用链路。以 SimpleExecutor.doQuery() 为例,拆解调用流程:
调用链路源码追踪 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 @Override public <E> List<E> doQuery (MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { Statement stmt = null ; try { Configuration config = ms.getConfiguration(); StatementHandler handler = config.newStatementHandler(this .wrapper, ms, parameter, rowBounds, resultHandler, boundSql); stmt = prepareStatement(handler, ms.getStatementLog()); return handler.query(stmt, resultHandler); } finally { closeStatement(stmt); } } private Statement prepareStatement (StatementHandler handler, Log statementLog) throws SQLException { Connection conn = getConnection(statementLog); Statement stmt = handler.prepare(conn, transaction.getTimeout()); handler.parameterize(stmt); return stmt; }
完整流程梳理 sequenceDiagram
participant Executor(执行器)
participant RoutingStatementHandler(路由)
participant PreparedStatementHandler(具体执行器)
participant ParameterHandler(参数处理器)
participant ResultSetHandler(结果处理器)
participant 数据库
Executor->>RoutingStatementHandler: 创建实例(传入 statementType)
RoutingStatementHandler->>PreparedStatementHandler: 选择 delegate(PREPARED)
Executor->>PreparedStatementHandler: 调用 prepare()
PreparedStatementHandler->>数据库: 创建 PreparedStatement(预编译 SQL)
PreparedStatementHandler->>数据库: 设置超时/fetchSize
Executor->>PreparedStatementHandler: 调用 parameterize()
PreparedStatementHandler->>ParameterHandler: 委托 setParameters()
ParameterHandler->>数据库: 为 ? 绑定参数
Executor->>PreparedStatementHandler: 调用 query()
PreparedStatementHandler->>数据库: 执行 SQL,返回 ResultSet
PreparedStatementHandler->>ResultSetHandler: 委托 handleResultSets()
ResultSetHandler->>ResultSetHandler: 解析 ResultSet 为 Java 对象
ResultSetHandler->>Executor: 返回结果列表
Executor->>数据库: 关闭 Statement
实战配置与最佳实践 1. 配置 statementType:选择执行器 在 Mapper XML 或注解中通过 statementType 属性指定执行器,默认值为 PREPARED:
statementType
对应执行器
适用场景
STATEMENT
SimpleStatementHandler
无参数的静态 SQL
PREPARED
PreparedStatementHandler
带参数的预编译 SQL(默认)
CALLABLE
CallableStatementHandler
调用存储过程
配置示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 <select id ="selectAllUser" resultType ="user" statementType ="STATEMENT" > SELECT * FROM user </select > <select id ="selectUserById" resultType ="user" statementType ="PREPARED" > SELECT * FROM user WHERE id = #{id} </select > <select id ="callGetUser" statementType ="CALLABLE" resultType ="user" > {CALL get_user(#{id,mode=IN,jdbcType=INTEGER}, #{name,mode=OUT,jdbcType=VARCHAR})} </select >
2. 常见问题与解决方案 (1)存储过程输出参数无法获取
(2)批量执行效率低
(3)SQL 注入风险
问题原因 :使用 STATEMENT 执行带参数的 SQL(如 WHERE name = '${name}');
解决方案 :切换为 PREPARED,使用 #{} 占位符(自动防注入),避免 ${} 直接拼接 SQL。
总结 StatementHandler 是 MyBatis 数据库交互的 “核心执行者”,其设计体现了 MyBatis 对 JDBC 的优雅封装:
架构设计 :通过模板方法模式(BaseStatementHandler)封装通用逻辑,策略模式(RoutingStatementHandler)动态选择执行器,降低耦合,提升扩展性;
核心实现 :三个具体执行器对应 JDBC 三种 Statement,覆盖无参数、预编译、存储过程场景,满足不同业务需求;
协作关系 :与 Executor、ParameterHandler、ResultSetHandler 紧密协作,形成 “SQL 执行→参数设置→结果映射” 的完整闭环