MyBatis源码解读(四)执行SQL

MyBatis源码解读(四)执行SQL

MyBatis源码解读之执行SQL

1
List<User> list = mapper.selectUserByName("zhangsan");

由于所有的Mapper都是JDK动态代理对象,所以任意的方法都是执行触发管理类MapperProxy的invoke()方法。

问题1:引入MapperProxy为了解决什么问题?硬编码和编译时检查问题。它需要做的事情是:根据方法查找StatementID的问题。

问题2:进入到invoke方法的时候做了什么事情?它是怎么找到我们要执行的SQL的?

1、MapperProxy. invoke()

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
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// toString hashCode equals getClass等方法,无需走到执行SQL的流程
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else {
// 提升获取 mapperMethod 的效率,到 MapperMethodInvoker(内部接口) 的 invoke
// 普通方法会走到 PlainMethodInvoker(内部类) 的 invoke
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}

private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
try {
// Java8 中 Map 的方法,根据 key 获取值,如果值是 null,则把后面Object 的值赋给 key
// 如果获取不到,就创建
// 获取的是 MapperMethodInvoker(接口) 对象,只有一个invoke方法
// 根据method 去methodCache中获取 如果返回空 则用第二个参数填充
return methodCache.computeIfAbsent(method, m -> {
if (m.isDefault()) {
// 接口的默认方法(Java8),只要实现接口都会继承接口的默认方法,例如 List.sort()
try {
if (privateLookupInMethod == null) {
return new DefaultMethodInvoker(getMethodHandleJava8(method));
} else {
return new DefaultMethodInvoker(getMethodHandleJava9(method));
}
} catch (IllegalAccessException | InstantiationException | InvocationTargetException
| NoSuchMethodException e) {
throw new RuntimeException(e);
}
} else {
// 创建了一个 MapperMethod
return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}
});
} catch (RuntimeException re) {
Throwable cause = re.getCause();
throw cause == null ? re : cause;
}
}

1)首先判断是否需要去执行SQL,还是直接执行方法。Object本身的方法不需要去执行SQL,比如toString()、hashCode()、equals()、getClass()。

2)获取缓存

这里加入缓存是为了提升MapperMethod的获取速度,很巧妙的设计,缓存的使用在MyBatis中随处可见。

Map的computelfAbsent()方法:根据key获取值,如果值是null,则把后面Object的值赋给key。

Java8和Java9中的接口默认方法有特殊处理,返回DefaultMethodInvoker。 普通的方法返回的是PlainMethodInvoker,返回MapperMethod。

MapperMethod中有两个主要的属性:

1
2
3
4
// statement id (例如:com.gupaoedu.mapper.BlogMapper.selectBlogById) 和 SQL 类型
private final SqlCommand command;
// 方法签名,主要是返回值的类型
private final MethodSignature method;

SqlCommand 封装了 statement id,例如 com.dxysun.mybatis.mapper.UserMapper#selectUserByName 和SQL类型。

MethodSignature,主要封装是返回值的类型。

这两个属性都是MapperMethod的内部类,另外MapperMethod种定义了多种execute()方法。

2、MapperMethod. execute()

最后会调用到mapperMethod的execute方法

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
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) { // 根据SQL语句的类型调用SqlSession对应的方法
case INSERT: {
// 通过 ParamNameResolver 处理args[] 数组 将用户传入的实参和指定参数名称关联起来
Object param = method.convertArgsToSqlCommandParam(args);
// sqlSession.insert(command.getName(), param) 调用SqlSession的insert方法
// rowCountResult 方法会根据 method 字段中记录的方法的返回值类型对结果进行转换
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
// 返回值为空 且 ResultSet通过 ResultHandler处理的方法
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
// 返回值为 单一对象的方法
Object param = method.convertArgsToSqlCommandParam(args);
// 普通 select 语句的执行入口 >>
result = sqlSession.selectOne(command.getName(), param);
if (method.returnsOptional()
&& (result == null || !method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}

在这一步,根据不同的type (INSERT、UPDATE、DELETE、SELECT)和返回类型:

1)调用convertArgsToSqlCommandParam()将方法参数转换为SQL的参数。

2) 调用sqlSession的insert()、update()、delete()、selectOne()方法。我们以查询为例,会走到selectOne()方法。

1
2
3
4
// 返回值为 单一对象的方法
Object param = method.convertArgsToSqlCommandParam(args);
// 普通 select 语句的执行入口 >>
result = sqlSession.selectOne(command.getName(), param);

3、DefaultSqlSession. selectOne()

这里来到了对外的接口的默认实现类DefaultSqISession。

selectOne() 最终也是调用了selectList()

1
2
3
4
5
6
7
8
9
10
11
12
13
@Override
public <T> T selectOne(String statement, Object parameter) {
// 来到了 DefaultSqlSession
// Popular vote was to return null on 0 results and throw exception on too many.
List<T> list = this.selectList(statement, parameter);
if (list.size() == 1) {
return list.get(0);
} else if (list.size() > 1) {
throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
} else {
return null;
}
}

在SelectList() 中,先根据command name (StatementID) 从Configuration中拿到MappedStatement。

MappedStatement 里面有xml中增删改查标签配置的所有属性,包括id、statementType、 sqlSource、 useCache、入参、出参等等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Override
public <E> List<E> selectList(String statement, Object parameter) {
// 为了提供多种重载(简化方法使用),和默认值
// 让参数少的调用参数多的方法,只实现一次
return this.selectList(statement, parameter, RowBounds.DEFAULT);
}

@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
// 如果 cacheEnabled = true(默认),Executor会被 CachingExecutor装饰
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
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
/**
* @author Clinton Begin
* 对应的是 Mapper.xml 里面的一个增删改查的标签
* 参数类型,结果类型,SQL,方法属性都在这这对象中
*/
public final class MappedStatement {

private String resource;
private Configuration configuration;
private String id;
private Integer fetchSize;
private Integer timeout;
private StatementType statementType;
private ResultSetType resultSetType;
private SqlSource sqlSource;
private Cache cache;
private ParameterMap parameterMap;
private List<ResultMap> resultMaps;
private boolean flushCacheRequired;
private boolean useCache;
private boolean resultOrdered;
private SqlCommandType sqlCommandType;
private KeyGenerator keyGenerator;
private String[] keyProperties;
private String[] keyColumns;
private boolean hasNestedResultMaps;
private String databaseId;
private Log statementLog;
private LanguageDriver lang;
private String[] resultSets;
...
}

然后执行了Executor的query()方法。

Executor是第二步openSession的时候创建的,创建了执行器基本类型之后,依次执行了二级缓存装饰,和插件拦截。

所以,如果有被插件拦截,这里会先走到插件的逻辑。如果没有显式地在settings中配置cacheEnabled=false,再走到CachingExecutor的逻辑,然后会走到 BaseExecutor的query()方法。

4、CachingExecutor. query()

(1)创建CacheKey

二级缓存的CacheKey是怎么构成的呢?或者说,什么样的查询才能确定是同一个查询呢?

在BaseExecutor的createCacheKey方法中,用到了六个要素:

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
@Override
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
if (closed) {
throw new ExecutorException("Executor was closed.");
}
CacheKey cacheKey = new CacheKey();
// -1381545870:4796102018:com.dxysun.mapper.BlogMapper.selectBlogById:0:2147483647:select * from blog where bid = ?:1:development
cacheKey.update(ms.getId()); // com.dxysun.mapper.BlogMapper.selectBlogById
cacheKey.update(rowBounds.getOffset()); // 0
cacheKey.update(rowBounds.getLimit()); // 2147483647 = 2^31-1
cacheKey.update(boundSql.getSql());
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
// mimic DefaultParameterHandler logic
for (ParameterMapping parameterMapping : parameterMappings) {
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) {
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);
}
cacheKey.update(value); // development
}
}
if (configuration.getEnvironment() != null) {
// issue #176
cacheKey.update(configuration.getEnvironment().getId());
}
return cacheKey;
}

也就是说,方法相同、翻页偏移相同、SQL相同、参数值相同、数据源环境相同,才会被认为是同一个查询。

CacheKey的实际值举例(toString()生成的) ,debug可以看到:

1
-1381545870:4796102018:com.dxysun.mapper.BlogMapper.selectBlogByld:0:2147483647select*from blog where bid =?:l:development

注意看一下CacheKey的属性,里面有一个List按顺序存放了这些要素。

1
2
3
4
5
6
7
8
9
private static final int DEFAULT_MULTIPLIER = 37;
private static final int DEFAULT_HASHCODE = 17;

private final int multiplier;
private int hashcode;
private long checksum;
private int count;
// 8/21/2017 - Sonarlint flags this as needing to be marked transient. While true if content is not serializable, this is not always true and thus should not be marked transient.
private List<Object> updateList;

怎么比较两个CacheKey是否相等呢?

如果一上来就是依次比较六个要素是否相等,就要比较6次,这样效率不高。有没有更高效的方法呢?

继承Obiect每个类,都有hashCode()方法,用来生成哈希码,它是用来在集合中快速判重的。

在生成CacheKey的时候(update方法),也更新了CacheKey的hashCode,它是用乘法哈希生成的(基数baseHashCode=17,乘法因子multiplier=37)。

1
2
3
4
5
6
7
8
9
10
11
12
public void update(Object object) {
// 加法哈希
int baseHashCode = object == null ? 1 : ArrayUtil.hashCode(object);

count++;
checksum += baseHashCode;
baseHashCode *= count;

hashcode = multiplier * hashcode + baseHashCode;

updateList.add(object);
}

Object中的hashCode() 是一个本地方法,通过随机数算法生成(OpenJDK8,默认,可以通过-XX:hashCode修改)。

CacheKey中的hashCode()方法进行了重写,返回自己生成的hashCode。

为什么要用37作为乘法因子呢?跟String中的31类似。

CacheKey中的equals也进行了重写,比较CacheKey是否相等。

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
@Override
public boolean equals(Object object) {
// 同一个对象
if (this == object) {
return true;
}
// 被比较的对象不是 CacheKey
if (!(object instanceof CacheKey)) {
return false;
}

final CacheKey cacheKey = (CacheKey) object;

// hashcode 不相等
if (hashcode != cacheKey.hashcode) {
return false;
}
// checksum 不相等
if (checksum != cacheKey.checksum) {
return false;
}
// count 不相等
if (count != cacheKey.count) {
return false;
}

for (int i = 0; i < updateList.size(); i++) {
Object thisObject = updateList.get(i);
Object thatObject = cacheKey.updateList.get(i);
if (!ArrayUtil.equals(thisObject, thatObject)) {
return false;
}
}
return true;
}

如果哈希值(乘法哈希)、校验值(加法哈希)、要素个数任何一个不相等,都不是同一个查询,最后才循环比较要素,防止哈希碰撞。

CacheKey生成之后,调用CachingExecutor的另一个query()方法。

(2)处理二级缓存

首先从MappedStatement 中取出cache对象,判断cache对象是否为空,如果为空,则没有查询二级缓存、写入二级缓存的流程。

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> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
Cache cache = ms.getCache();
// cache 对象是在哪里创建的? XMLMapperBuilder类 xmlconfigurationElement()
// 由 <cache> 标签决定
if (cache != null) {
// flushCache="true" 清空一级二级缓存 >>
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
// 获取二级缓存
// 缓存通过 TransactionalCacheManager、TransactionalCache 管理
@SuppressWarnings("unchecked")
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
// 写入二级缓存
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
// 走到 SimpleExecutor | ReuseExecutor | BatchExecutor
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

Cache对象是什么时候创建的呢?

用来解析Mapper.xml的XMLMapperBuilder类,cacheElement()方法:

1
cacheElement(context.evalNode("cache"));

只有Mapper.xml中的<cache>标签不为空才解析。

二级缓存为什么要用TransactionalCacheManager来管理?

思考一个问题,在一个事务中:

1、首先插入一条数据(没有提交),此时二级缓存会被清空。

2、在这个事务中查询数据,写入二级缓存。

3、提交事务,出现异常,数据回滚。

此时出现了数据库没有这条数据,但是二级缓存有这条数据的情况,所以MyBatis的二级缓存需要跟事务关联起来。

为什么一级缓存不这么做?

因为一个session就是一个事务,事务回滚,会话就结束了,缓存也清空了,不存在读到一级缓存中脏数据的情况。

二级缓存是跨session的,也就是跨事务的,才有可能出现对同一个方法的不同事务访问。

1)写入二级缓存

1
tcm.putObject(cache, key, list); // issue #578 and #116

从map中拿出TransactionalCache对象,把value添加到待提交的Map。此时缓存还没有真正地写入。

1
2
3
4
@Override
public void putObject(Object key, Object object) {
entriesToAddOnCommit.put(key, object);
}

只有事务提交的时候缓存才真正写入(close或者commit最后分析)。

2)获取二级缓存

1
List<E> list = (List<E>) tcm.getObject(cache, key);

从map中拿出TransactionalCache对象,这个对象也是对PerpetualCache层层装饰的缓存对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Override
public Object getObject(Object key) {
// issue #116
Object object = delegate.getObject(key);
if (object == null) {
entriesMissedInCache.add(key);
}
// issue #146
if (clearOnCommit) {
return null;
} else {
return object;
}
}

5、BaseExecutor. query()

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
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
// 异常体系之 ErrorContext
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
if (queryStack == 0 && ms.isFlushCacheRequired()) {
// flushCache="true"时,即使是查询,也清空一级缓存
clearLocalCache();
}
List<E> list;
try {
// 防止递归查询重复处理缓存
queryStack++;
// 查询一级缓存
// ResultHandler 和 ResultSetHandler的区别
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
// 真正的查询流程
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}

(1)清空本地缓存

queryStack用于记录查询栈,防止递归查询重复处理缓存。 flushCache=true的时候,会先清理本地缓存(一级缓存)

1
2
3
4
if (queryStack == 0 && ms.isFlushCacheRequired()) {
// flushCache="true"时,即使是查询,也清空一级缓存
clearLocalCache();
}

如果没有缓存,会从数据库查询:queryFromDatabase()

1
2
// 真正的查询流程
list= queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);

(2)从数据库查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
// 先占位
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
// 三种 Executor 的区别,看doUpdate
// 默认Simple
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
// 移除占位符
localCache.removeObject(key);
}
// 写入一级缓存
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}

a)缓存

先在缓存用占位符占为,执行查询后,移除占位符,放入数据。

1
2
// 先占位
localCache.putObject(key, EXECUTION_PLACEHOLDER);

b)查询

执行Executor的doQuery();默认是SimpleExecutor.

1
2
3
// 三种 Executor 的区别,看doUpdate
// 默认Simple
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);

6、 SimpleExecutor.doQuery()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

@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();
// 注意,已经来到SQL处理的关键对象 StatementHandler >>
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
// 获取一个 Statement对象
stmt = prepareStatement(handler, ms.getStatementLog());
// 执行查询
return handler.query(stmt, resultHandler);
} finally {
// 用完就关闭
closeStatement(stmt);
}
}

1)创建StatementHandler

在configuration.newStatementHandler() 中,new一个StatementHandler,先得到RoutingStatementHandler

RoutingStatementHandler里面没有任何的实现,是用来创建基本的StatementHandler的。

这里会根据MappedStatement里面的statementType决定 StatementHandler的类型。

默认是PREPARED(STATEMENT、PREPARED、 CALLABLE)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
// StatementType 是怎么来的? 增删改查标签中的 statementType="PREPARED",默认值 PREPARED
switch (ms.getStatementType()) {
case STATEMENT:
delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case PREPARED:
// 创建 StatementHandler 的时候做了什么? >>
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());
}

}

StatementHandler里面包含了处理参数的ParameterHandler和处理结果集的ResultSetHandler。

这两个对象都是在上面new的时候创建的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
this.configuration = mappedStatement.getConfiguration();
this.executor = executor;
this.mappedStatement = mappedStatement;
this.rowBounds = rowBounds;

this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
this.objectFactory = configuration.getObjectFactory();

if (boundSql == null) { // issue #435, get the key before calculating the statement
generateKeys(parameterObject);
boundSql = mappedStatement.getBoundSql(parameterObject);
}

this.boundSql = boundSql;

// 处理参数的ParameterHandler和处理结果集的ResultSetHandler
this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
}

这三个对象都是可以被插件拦截的四大对象之一,所以在创建之后都要用拦截器进行包装的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
// 植入插件逻辑(返回代理对象)
parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
return 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);
// 植入插件逻辑(返回代理对象)
resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
return 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);
// 植入插件逻辑(返回代理对象)
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}

2)创建Statement

1
2
3
4
5
6
7
8
9
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
// 获取 Statement 对象
stmt = handler.prepare(connection, transaction.getTimeout());
// 为 Statement 设置参数
handler.parameterize(stmt);
return stmt;
}

用new出来的StatementHandler创建Statement对象,如果有插件包装,会先走到被拦截的业务逻辑。

1
stmt = handler.prepare(connection, transaction.getTimeout());

prepareStatement() 方法对语句进行预编译,处理参数

1
handler.parameterize(stmt);

这里面会调用parameterHandler设置参数,如果有插件包装,会先走到被拦截的业务逻辑。

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

3)执行的StatementHandler的query() 方法

执行RoutingStatementHandler的query()方法,deleqate委派,最终执行PreparedStatementHandler的query()方法。

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

4)执行PreparedStatement的execute()方法

后面就是JDBC包中的PreparedStatement的执行了。

5) ResultSetHandler处理结果

如果有插件包装,会先走到被拦截的业务逻辑。

1
return resultSetHandler.handleResultSets(ps);

怎么把ResultSet转换成List<Object>

ResultSetHandler 只有一个实现类: DefaultResultSetHandler。也就是执行DefaultResultSetHandler的handleResultSets()方法。

首先会先拿到第一个结果集,如果没有配置一个查询返回多个结果集的情况,一般只有一个结果集。

如果下面的这个while循环我们也不用,就是执行一次。

然后会调用handleResultSet()方法

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
@Override
public List<Object> handleResultSets(Statement stmt) throws SQLException {
ErrorContext.instance().activity("handling results").object(mappedStatement.getId());

// 该集合用于保存映射结果集得到的结果对象
final List<Object> multipleResults = new ArrayList<>();

int resultSetCount = 0;
// 获取第一个ResultSet对象
ResultSetWrapper rsw = getFirstResultSet(stmt);

// <resultMap>会被解析为 ResultMap对象,并保存到 mappedStatement.resultMaps 集合中
// 如果SQL节点能够产生多个ResultSet,那么我们可以在SQL节点的 resultMap属性中配置多个<resultMap>节点的id
// 他们之间通过','分割,实现对多个结果集的映射
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
// 获取 resultMap的个数
int resultMapCount = resultMaps.size();
// 验证 如果结果集不为空 那么 resultMaps不能为空,否则抛异常
validateResultMapsCount(rsw, resultMapCount);
// 遍历 resultMaps 集合
while (rsw != null && resultMapCount > resultSetCount) {
// 获取循环的 ResultMap对象
ResultMap resultMap = resultMaps.get(resultSetCount);
// 处理单个ResultSet对象 也就是根据resultMap中定义的映射规则对ResultSet进行映射
// 并将结果添加到 multipleResults 中 对单个结果集的映射
handleResultSet(rsw, resultMap, multipleResults, null);
rsw = getNextResultSet(stmt);// 获取下一个结果集
cleanUpAfterHandlingResultSet(); // 清空 nestedResultObjects
resultSetCount++;
}

// resultSets 该属性仅仅对多结果集的情况适用,该属性将列出语句执行后返回的结果集,并给每个结果集一个名称
// 名称是逗号分隔的 可以先不关注
String[] resultSets = mappedStatement.getResultSets();
if (resultSets != null) {
while (rsw != null && resultSetCount < resultSets.length) {
ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
if (parentMapping != null) {
String nestedResultMapId = parentMapping.getNestedResultMapId();
ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
handleResultSet(rsw, resultMap, null, parentMapping);
}
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
}

return collapseSingleResultList(multipleResults);
}

总结

MyBatis 核心对象

对象 相关对象 作用
Configuration MapperRegistry
TypeAliasRegistry
TypeHandlerRegistry
包含了MyBatis的所有的配置信息
SqlSession SqlSessionFactory
DefaultSqlSession
对操作数据库的增删改查的API进行了封装,提供给应用层使用
BaseExecutor SimpleExecutor
BatchExecutor
ReuseExecutor
MyBatis执行器,是MyBatis调度的核心,负责SQL语句的生成和查查询缓存的维护
StatementHandler BaseStatementHandler
SimpleStatementHandler
PreparedStatementHandler
CallableStatementHandler
封装了JDBCStatement操作,负责对JDBCstatement的操作,如设置参数、将Statement结果集转换成List集合
ParameterHandler DefaultParameterHandler 把用户传递的参数转换成JDBC Statement所需要的参数
ResultSetHandler DefaultResultSetHandler 把JDBC返回的ResultSet结果集对象转换成List类型的集合
MapperProxy MapperProxyFactory 触发管理类,用于代理Mapper接口方法
MappedStatement SqlSource
BoundSql
MappedStatement维护了一条<select|update|delete|insert>节点的封装,表示一条SQL包括了SQL信息、入参信息、出参信息
打赏

请我喝杯咖啡吧~

支付宝
微信