0%

mybatis之StatementHandler

MyBatis StatementHandler 深度解析:数据库交互的核心执行者

StatementHandler 是 MyBatis 中直接与数据库交互的核心接口,负责管理 JDBC 的 Statement 对象(Statement/PreparedStatement/CallableStatement),协调 ParameterHandler(参数设置)和 ResultSetHandler(结果映射),完成 “SQL 预编译→参数绑定→SQL 执行→结果处理” 的全流程。从 “接口定位→架构设计→核心实现→调用链路” 四个维度,彻底拆解 StatementHandler 的工作机制,并揭示其背后的设计模式。

StatementHandler 核心定位与接口规范

核心职责

StatementHandler 是 MyBatis 与数据库交互的 “最后一公里”,核心职责可概括为 5 点:

  1. 创建 Statement 对象:根据 SQL 类型(无参数 / 预编译 / 存储过程)创建对应的 JDBC Statement
  2. SQL 预编译:对预编译 SQL(PreparedStatement)进行编译,提升执行效率;
  3. 参数绑定:委托 ParameterHandler 为 SQL 占位符(?)设置参数;
  4. SQL 执行:执行 select/insert/update/delete 及批量操作;
  5. 结果转发:将执行结果(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 {
// 1. 从数据库连接中创建 Statement 对象(预编译 SQL 在此步骤)
Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException;

// 2. 为 Statement 绑定参数(仅 PreparedStatement/CallableStatement 需实现)
void parameterize(Statement statement) throws SQLException;

// 3. 批量执行 SQL(添加 SQL 到批量队列)
void batch(Statement statement) throws SQLException;

// 4. 执行 update/insert/delete,返回受影响行数
int update(Statement statement) throws SQLException;

// 5. 执行 select,返回结果列表(委托 ResultSetHandler 处理)
<E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException;

// 6. 执行 select,返回游标(流式读取大数据量)
<E> Cursor<E> queryCursor(Statement statement) throws SQLException;

// 7. 获取当前 SQL 对应的 BoundSql(封装 SQL 语句、参数映射等)
BoundSql getBoundSql();

// 8. 获取当前 StatementHandler 关联的 ParameterHandler
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 作为 “策略路由”,根据 MappedStatementstatementType 选择对应的 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; // SQL 元信息
protected final RowBounds rowBounds; // 分页参数
protected final BoundSql boundSql; // SQL 封装对象

// 构造函数:初始化依赖组件(参数/结果处理器)
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;

// 1. 创建参数处理器(DefaultParameterHandler)
this.parameterHandler = configuration.newParameterHandler(ms, parameter, boundSql);
// 2. 创建结果处理器(DefaultResultSetHandler)
this.resultSetHandler = configuration.newResultSetHandler(executor, ms, rowBounds, parameterHandler, resultHandler, boundSql);

// 3. 生成自增主键(如 MySQL 的 auto_increment,在 SQL 执行前获取)
if (boundSql == null) {
generateKeys(parameter);
this.boundSql = ms.getBoundSql(parameter);
}
}

// ------------------------------ 模板方法:prepare() ------------------------------
@Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
ErrorContext.instance().sql(boundSql.getSql());
Statement statement = null;
try {
// 1. 子类实现:创建具体的 Statement 对象(差异化逻辑)
statement = instantiateStatement(connection);
// 2. 通用逻辑:设置 Statement 超时时间(全局配置或 Mapper 配置)
setStatementTimeout(statement, transactionTimeout);
// 3. 通用逻辑:设置 fetchSize(每次从 ResultSet 读取的行数,优化大数据量查询)
setFetchSize(statement);
return statement;
} catch (SQLException e) {
closeStatement(statement); // 异常时关闭 Statement,避免泄漏
throw e;
}
}

// ------------------------------ 抽象方法:子类实现差异化逻辑 ------------------------------
// 抽象方法:创建具体的 Statement(Statement/PreparedStatement/CallableStatement)
protected abstract Statement instantiateStatement(Connection connection) throws SQLException;

// ------------------------------ 通用工具方法 ------------------------------
// 设置超时时间:优先使用 Mapper 配置,其次用全局配置
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);
}

// 设置 fetchSize:优化大数据量查询(减少数据库交互次数)
protected void setFetchSize(Statement stmt) throws SQLException {
Integer fetchSize = mappedStatement.getFetchSize() != null ?
mappedStatement.getFetchSize() : configuration.getDefaultFetchSize();
if (fetchSize != null) {
stmt.setFetchSize(fetchSize);
}
}

// 关闭 Statement(避免连接泄漏)
protected void closeStatement(Statement statement) {
if (statement != null) {
try {
statement.close();
} catch (SQLException e) { /* 忽略关闭异常 */ }
}
}

// 生成自增主键(如 Jdbc3KeyGenerator 预获取主键)
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”;
  • 依赖注入:在构造函数中创建 ParameterHandlerResultSetHandler,建立 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);
}

// ------------------------------ 执行 update/insert/delete ------------------------------
@Override
public int update(Statement statement) throws SQLException {
String sql = boundSql.getSql();
Object parameterObject = boundSql.getParameterObject();
KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
int rows;

// 处理自增主键(Jdbc3KeyGenerator:执行 SQL 时获取主键)
if (keyGenerator instanceof Jdbc3KeyGenerator) {
statement.execute(sql, Statement.RETURN_GENERATED_KEYS); // 执行 SQL 并返回主键
rows = statement.getUpdateCount(); // 受影响行数
keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject); // 设置主键到实体
}
// 处理 selectKey 生成的主键(如 Oracle 序列)
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;
}

// ------------------------------ 执行 select ------------------------------
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
String sql = boundSql.getSql();
statement.execute(sql); // 执行无参数 SQL
return resultSetHandler.handleResultSets(statement); // 委托结果处理器映射
}

// ------------------------------ 批量执行 ------------------------------
@Override
public void batch(Statement statement) throws SQLException {
String sql = boundSql.getSql();
statement.addBatch(sql); // 将 SQL 加入批量队列(无参数,直接添加 SQL 字符串)
}

// ------------------------------ 抽象方法实现:创建 Statement ------------------------------
@Override
protected Statement instantiateStatement(Connection connection) throws SQLException {
// 根据 ResultSet 类型创建 Statement(默认 TYPE_FORWARD_ONLY,CONCUR_READ_ONLY)
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);
}

// ------------------------------ 执行 update/insert/delete ------------------------------
@Override
public int update(Statement statement) throws SQLException {
PreparedStatement ps = (Statement) statement;
ps.execute(); // 执行预编译 SQL(已绑定参数)
int rows = ps.getUpdateCount(); // 受影响行数
Object parameterObject = boundSql.getParameterObject();
// 处理自增主键(执行后设置到实体)
mappedStatement.getKeyGenerator().processAfter(executor, mappedStatement, ps, parameterObject);
return rows;
}

// ------------------------------ 执行 select ------------------------------
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute(); // 执行预编译 SQL
return resultSetHandler.handleResultSets(ps); // 映射结果
}

// ------------------------------ 批量执行 ------------------------------
@Override
public void batch(Statement statement) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.addBatch(); // 批量添加预编译 SQL(参数已绑定,仅需添加执行指令)
}

// ------------------------------ 抽象方法实现:创建 PreparedStatement(预编译) ------------------------------
@Override
protected Statement instantiateStatement(Connection connection) throws SQLException {
String sql = boundSql.getSql();
KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();

// 1. 若为自增主键(Jdbc3KeyGenerator),创建支持返回主键的 PreparedStatement
if (keyGenerator instanceof Jdbc3KeyGenerator) {
String[] keyColumnNames = mappedStatement.getKeyColumns();
return keyColumnNames == null ?
connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS) : // 返回默认主键列
connection.prepareStatement(sql, keyColumnNames); // 指定主键列
}
// 2. 普通预编译 SQL:按 ResultSet 类型创建
else if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {
return connection.prepareStatement(sql);
} else {
return connection.prepareStatement(
sql,
mappedStatement.getResultSetType().getValue(),
ResultSet.CONCUR_READ_ONLY
);
}
}

// ------------------------------ 参数绑定:委托 ParameterHandler ------------------------------
@Override
public void parameterize(Statement statement) throws SQLException {
// 调用 DefaultParameterHandler.setParameters(),为 ? 占位符设置参数
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);
}

// ------------------------------ 执行存储过程(update 类型) ------------------------------
@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);
// 处理输出参数(将存储过程的 OUT 参数设置到实体)
resultSetHandler.handleOutputParameters(cs);
return rows;
}

// ------------------------------ 执行存储过程(select 类型) ------------------------------
@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;
}

// ------------------------------ 抽象方法实现:创建 CallableStatement ------------------------------
@Override
protected Statement instantiateStatement(Connection connection) throws SQLException {
String sql = boundSql.getSql(); // 存储过程调用语句(如 {CALL get_user(?, ?)})
// 按 ResultSet 类型创建 CallableStatement
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); // 1. 注册 OUT/INOUT 参数
parameterHandler.setParameters(cs); // 2. 设置 IN/INOUT 参数(委托 ParameterHandler)
}

// ------------------------------ 注册输出参数(关键:告知 JDBC 输出参数类型) ------------------------------
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);
// 仅处理 OUT/INOUT 参数
if (pm.getMode() == ParameterMode.OUT || pm.getMode() == ParameterMode.INOUT) {
JdbcType jdbcType = pm.getJdbcType();
if (jdbcType == null) {
throw new ExecutorException("OUT 参数必须指定 JdbcType:" + pm.getProperty());
}
// 注册输出参数:索引(i+1,JDBC 索引从 1 开始)、JDBC 类型、精度(可选)
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,而是根据 MappedStatementstatementType 属性,动态选择对应的 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 {
// 委托执行器(真正执行 SQL 的具体实现)
private final StatementHandler delegate;

// 构造函数:根据 statementType 选择 delegate
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter,
RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
switch (ms.getStatementType()) {
case STATEMENT:
// statementType="STATEMENT" → 选择 SimpleStatementHandler
delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case PREPARED:
// statementType="PREPARED"(默认)→ 选择 PreparedStatementHandler
delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case CALLABLE:
// statementType="CALLABLE" → 选择 CallableStatementHandler
delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
default:
throw new ExecutorException("未知 statementType:" + ms.getStatementType());
}
}

// ------------------------------ 所有接口方法均委托给 delegate 实现 ------------------------------
@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);
}

// 其他方法(batch/query 等)均类似,直接委托 delegate
}
核心价值:
  • 解耦选择逻辑:上层(如 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
// SimpleExecutor.doQuery()
@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();
// 1. 创建 RoutingStatementHandler(根据 statementType 选择 delegate)
StatementHandler handler = config.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
// 2. 调用 StatementHandler.prepare():创建 Statement + 预编译 SQL
// 调用 StatementHandler.parameterize():绑定参数(仅 Prepared/Callable)
stmt = prepareStatement(handler, ms.getStatementLog());
// 3. 调用 StatementHandler.query():执行 SQL + 委托 ResultSetHandler 映射结果
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt); // 关闭 Statement
}
}

// SimpleExecutor.prepareStatement():准备 Statement 并绑定参数
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Connection conn = getConnection(statementLog);
Statement stmt = handler.prepare(conn, transaction.getTimeout()); // 1. 预编译 SQL,创建 Statement
handler.parameterize(stmt); // 2. 绑定参数(仅 Prepared/Callable 有效)
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
<!-- 1. 无参数 SQL:使用 STATEMENT -->
<select id="selectAllUser" resultType="user" statementType="STATEMENT">
SELECT * FROM user
</select>

<!-- 2. 带参数 SQL:使用 PREPARED(默认,可省略) -->
<select id="selectUserById" resultType="user" statementType="PREPARED">
SELECT * FROM user WHERE id = #{id}
</select>

<!-- 3. 调用存储过程:使用 CALLABLE -->
<select id="callGetUser" statementType="CALLABLE" resultType="user">
{CALL get_user(#{id,mode=IN,jdbcType=INTEGER}, #{name,mode=OUT,jdbcType=VARCHAR})}
</select>

2. 常见问题与解决方案

(1)存储过程输出参数无法获取
  • 问题原因:未指定 mode=OUT 或未配置 jdbcType

  • 解决方案:在参数中明确mode和jdbcType:

    1
    2
    3
    4
    5
    6
    7
    <select id="callGetUser" statementType="CALLABLE">
    {CALL get_user(
    #{id,mode=IN,jdbcType=INTEGER}, <!-- 输入参数 -->
    #{name,mode=OUT,jdbcType=VARCHAR},<!-- 输出参数:必须指定 mode 和 jdbcType -->
    #{age,mode=OUT,jdbcType=INTEGER}
    )}
    </select>
(2)批量执行效率低
  • 问题原因:使用 SimpleStatementHandler 批量执行(需重复拼接 SQL);

  • 解决方案:使用PreparedStatementHandler + BatchExecutor,复用预编译 SQL:

    1
    2
    3
    4
    5
    6
    7
    8
    // 批量插入示例
    try (SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH)) {
    UserMapper mapper = session.getMapper(UserMapper.class);
    for (User user : userList) {
    mapper.insertUser(user); // 使用 PREPARED,参数绑定后加入批量队列
    }
    session.commit(); // 批量提交
    }
(3)SQL 注入风险
  • 问题原因:使用 STATEMENT 执行带参数的 SQL(如 WHERE name = '${name}');
  • 解决方案:切换为 PREPARED,使用 #{} 占位符(自动防注入),避免 ${} 直接拼接 SQL。

总结

StatementHandler 是 MyBatis 数据库交互的 “核心执行者”,其设计体现了 MyBatis 对 JDBC 的优雅封装:

  1. 架构设计:通过模板方法模式(BaseStatementHandler)封装通用逻辑,策略模式(RoutingStatementHandler)动态选择执行器,降低耦合,提升扩展性;
  2. 核心实现:三个具体执行器对应 JDBC 三种 Statement,覆盖无参数、预编译、存储过程场景,满足不同业务需求;
  3. 协作关系:与 Executor、ParameterHandler、ResultSetHandler 紧密协作,形成 “SQL 执行→参数设置→结果映射” 的完整闭环

欢迎关注我的其它发布渠道