MyBatis:SqlSession下的四大对象

Mapper 通过类名和方法名就可以匹配到配置的SQL。

Mapper 执行的过程是通过 ExecutorStatementHandlerParameterHandlerResultHandler 来完成数据库操作和结果返回的。

四大对象

  • Executor 代表执行器,由它来调度 StatementHandlerParameterHandlerResultHandler 等来执行对应的 SQL。
  • StatementHandler 数据库会话器,作用是使用数据库的 Statement(PreparedStatement)执行操作,它是四大对象的核心,起到承上启下的作用。
  • ParameterHandler 用于 SQL 对参数的处理。
  • ResultHandler 是进行最后数据集(ResultSet)的封装返回处理的。

这四大对象的都是在 Configuration 类中创建的,从这可以看出 Configuration 类是 MyBatis 的核心。

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
public class Configuration {

//.......省略.......

// 参数处理器
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject,
BoundSql boundSql) {
ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement,
parameterObject, boundSql);
return (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
}

// 接口处理器
public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds,
ParameterHandler parameterHandler, ResultHandler resultHandler, BoundSql boundSql) {
ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler,
resultHandler, boundSql, rowBounds);
return (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
}

// 数据库会话器
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement,
Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject,
rowBounds, resultHandler, boundSql);
return (StatementHandler) interceptorChain.pluginAll(statementHandler);
}

// 执行器
public Executor newExecutor(Transaction transaction) {
return newExecutor(transaction, defaultExecutorType);
}

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
return (Executor) interceptorChain.pluginAll(executor);
}

执行器

执行器(Executor)是一个真正执行 Java 和 数据库交互的东西,起到至关重要的作用。

在 MyBatis 中存在三种执行器,默认在 MyBatis 的 Configuration 类中创建执行器对象,配置文件 setting 元素的属性是 defaultExecutorType。

  • SIMPLE,简易执行器,默认的执行器。
  • REUSE,是一种执行器重用预处理语句。
  • BATCH,执行器重要语句和批量更新,它是针对批量专用的执行器。

创建执行器 Executor 源码逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
return (Executor) interceptorChain.pluginAll(executor);
}

配置文件 setting 属性配置:

1
2
3
4
5
6
7
<settings>
<setting name="defaultExecutorType" value="SIMPLE"/>
</settings>
<!--批量更新-->
<settings>
<setting name="defaultExecutorType" value="BATCH"/>
</settings>

MyBatis 会根据配置的类型去确定需要创建三种执行器中的哪一种,在创建对象后,会执行下面这样一行代码。

1
interceptorChain.pluginAll(executor);

这就是 MyBatis 的插件,这里它将为我们构建一层层的动态代理对象。在调度真实的 Executor 方法之前执行配置插件的代码可以修改。

SIMPLE执行器为例,看看执行器方法内部。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
  @Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler,
BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler,
boundSql);
// 返回PreparedStatement
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt);
return stmt;
}
}

MyBatis 根据 Configuration 来构建 StatementHandler,然后使用 PreparedStatement 方法对 SQL 编译并对参数进行初始化。实现过程是它调用了 StatementHandlerprepare() 方法进行预编译和基础设置,然后通过 StatementHandlerparameterize() 来设置参数并执行,ResultHandler 再组装查询结果返回给调用方来完成一次查询。

数据库会话器

数据库会话器(StatementHandler)是专门处理数据库会话的。下面看看 MyBatis 如何创建 StatementHandler 的。

Configuration 类里创建 StatementHandler,很显然,创建的真实对象是 RoutingStatementHandler 对象,它实现了接口 StatementHandler,和 Executor 一样,用代理对象做一层层的封装。。

1
2
3
4
5
6
7
8
public class Configuration {
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement,
Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject,
rowBounds, resultHandler, boundSql);
return (StatementHandler) interceptorChain.pluginAll(statementHandler);
}
}

在初始化 RoutingStatementHandler 对象的时候它会根据上下文环境决定创建哪个 StatementHandler 对象。

RoutingStatementHandler 不是我们真实的服务对象,它是通过适配模式找到应的的 StatementHandler 来执行的。

StatementHandlerExecutor 一样分为三种:SimpleStatementHandlerPreparedStatementHandlerCallableStatementHandler

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
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("Unknown statement type: " + 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 void batch(Statement statement) throws SQLException {
delegate.batch(statement);
}

@Override
public int update(Statement statement) throws SQLException {
return delegate.update(statement);
}

@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
return delegate.query(statement, resultHandler);
}

@Override
public <E> Cursor<E> queryCursor(Statement statement) throws SQLException {
return delegate.queryCursor(statement);
}

@Override
public BoundSql getBoundSql() {
return delegate.getBoundSql();
}

@Override
public ParameterHandler getParameterHandler() {
return delegate.getParameterHandler();
}
}

数据会话器定义了一个对象的适配器 delegate,它是一个 StatementHandler 接口对象,构造方法根据配置来适配对应的 StatementHandler 对象。它的作用是给实现类对象的使用提供一个统一,简易的使用适配器。此为对象的适配模式,可以让我们使用现有的类和方法对象提供服务,也可以根据实际需求对象屏蔽一些方法,甚至加入新的服务。

以最常用的 PreparedStatementHandler 为例,看看 MyBatis 早怎么执行查询的。看看它的主要三个方法 prepareparameterizequery

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public abstract class BaseStatementHandler implements StatementHandler {

@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;
} catch (Exception e) {
closeStatement(statement);
throw new ExecutorException("Error preparing statement. Cause: " + e, e);
}
}

}

prepare: instantiateStatement 方法是对 SQL 进行预编译。还做了一些基础配置,如设置超时、获最的最大行数等。然后 Executor 调用 parameterize 方法去设置参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class PreparedStatementHandler extends BaseStatementHandler {

@Override
public void parameterize(Statement statement) throws SQLException {
parameterHandler.setParameters((PreparedStatement) statement);
}

@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
return resultSetHandler.handleResultSets(ps);
}

}

在执行 query 方法前,参数和SQL已经被 prepare 预编译了,参数已经在 parameterize 方法设置了,接下来只是执行 SQL,然后返回结果就可以了。结果可以看到使用了 ResultHandler 进行封装和返回。

一条查询SQL执行过程:

  1. Executor 先调用 StatementHandler 的 prepare 方法预编译,同时设置一些基本运行的参数。
  2. 然后调用 parameterize 方法设置参数,完成预编译。
  3. 跟着就是执行查询,update() 也是这样,最后如果需要结果,就用 ResultSetHandler 封装结果返回给调用者。

参数处理器

MyBatis 通过参数处理器 ParameterHandler 对预编译语句进行参数设置,作用很明显,就是完成对预编译参数的设置。

1
2
3
4
5
6
7
public interface ParameterHandler {

Object getParameterObject();

void setParameters(PreparedStatement ps) throws SQLException;

}

ParameterHandler 是个接口,提供了两个方法给重写,其中 getParameterObject() 方法的作用是返回参数对象,**setParameters()**作用是设置预编译 SQL 语句的参数。

MyBatis 为 ParameterHandler 提供了一个默认实现类 DefaultParameterHandler,看看其中 setParameters() 的实现。

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

public class DefaultParameterHandler implements ParameterHandler {

@Override
public void setParameters(PreparedStatement ps) {
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings != null) {
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
TypeHandler typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {
jdbcType = configuration.getJdbcTypeForNull();
}
try {
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException | SQLException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}
}
}
}
}

}

从代码中可以看到它是从 parameterObject 对象中取参数,然后使用 typeHandler 进行参数处理,如果有显式设置,则会根据签名注册的 typeHandler 对参数进行处理。而 typeHandler 也是在 MyBatis 初始化的时候,注册在 Configuration 里面的,需要的时候可以直接拿来用。这样就完成了参数设置。

结果处理器

结果处理器 ResultSetHandler 是对结果集进行封装返回。

ResultSetHandler接口:

1
2
3
4
5
6
7
8
9
public interface ResultSetHandler {

<E> List<E> handleResultSets(Statement stmt) throws SQLException;

<E> Cursor<E> handleCursorResultSets(Statement stmt) throws SQLException;

void handleOutputParameters(CallableStatement cs) throws SQLException;

}
  • handleResultSets:包装结果集。
  • handleCursorResultSets:数据库内处理游标结果集。
  • handleOutputParameters:处理存储过程输出参数。

MyBatis 同样提供了 ResultSetHandler 默认的实现类 DefaultResultSetHandler,默认情况下都是通过这个类进行处理。这个实现比较复杂,它涉及使用了 JAVASSIST 或者 CGLIB 作为延迟加载,然后通过 typeHandler 和 ObjectFactory 进行组装结果返回,而我们使用它时几乎不会需要改变它。

作者

光星

发布于

2023-06-25

更新于

2023-08-16

许可协议

评论