Mybatis-源码分析之sql执行

经过解析阶段,我们获得了一个填充完整的Configuration对象,对象中有一个名为mappedStatementsMap<String, MappedStatement> 用于存放解析好的sql配置,还有一个MapperRegistry记录mapper接口与其代理类工厂的映射关系。

执行一条查询之前,首先要获得执行查询的SqlSession对象,在此之前要拿到工厂类SqlSessionFactory的实例。

1、 获取SqlSession

使用配置对象创建出一个SqlSessionFactory,这里创建的是默认的实现类

1
2
3
4
//SqlSessionFactoryBuilder.class
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}

用户代码获取SqlSession

1
SqlSession session = sqlSessionFactory.openSession();

openSession方法最终会调用到

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//DefaultSqlSessionFactory.class
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
// 获取事务工厂
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(
environment);
// 创建事务
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 根据事务工厂和默认的执行器类型,创建执行器 >>
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
...
}
}

这里我们知道根据全局配置能直到,创建的事务类型是Jdbc事务,执行器类型默认是 SIMPLE

执行器创建代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
// 没做配置的情况下,默认创建SimpleExecutor
executor = new SimpleExecutor(this, transaction);
}
// 二级缓存开关,settings 中的 cacheEnabled 默认是 true,缓存装饰器
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
// 植入插件的逻辑,至此,四大对象已经全部拦截完毕
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}

植入插件的逻辑将在单独分析插件实现源码时进行展开。

回到openSqlSession方法,接下来使用创建好的执行器,创建出默认的DefaultSqlSession,这里我们可以分析出对象之间的关系:每次创建SqlSession的时候,都会创建一个新的执行器被它持有,sqlSession的查询动作最终都会由执行器来执行。

2、执行sql查询

贴一段用户代码执行查询的操作:

1
2
3
4
5
6
7
try {
BlogMapper mapper = session.getMapper(BlogMapper.class);
Blog blog = mapper.selectBlogById(1);
System.out.println(blog);
} finally {
session.close();
}

首先根据mapper接口,获取一个mapper对象,这里获取到的对象是mapper对应的代理类工厂生产的代理对象,跟源码看:

1
2
3
4
//DefaultSqlSession.class
public <T> T getMapper(Class<T> type) {
return configuration.getMapper(type, this);
}

继续跟进

1
2
3
4
5
//Configutation.class
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
//委托给mapperRegistry
return mapperRegistry.getMapper(type, sqlSession);
}

mapperRegistry是什么,前面分析过是映射mapper接口与代理工厂的地方

1
2
3
4
5
6
7
8
9
10
11
12
13
// MapperRegistry.class
public class MapperRegistry {
private final Configuration config;
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
...
try {
return mapperProxyFactory.newInstance(sqlSession);
}
...
}

MapperRegistry根据传入的mapper接口,拿到对应的代理工厂类,注意调用的过程中sqlSession一直再往里面传递,最终要在代理类的invoke方法中落地。那最终我们getmapper方法获取到一个代理对象:

1
2
3
4
5
6
7
8
9
// MapperProxyFactory
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
protected T newInstance(MapperProxy<T> mapperProxy) {
// 1:类加载器:2:被代理类实现的接口、3:实现了 InvocationHandler 的触发管理类
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
1
public class MapperProxy<T> implements InvocationHandler

MapperProxy实现了InvocationHandler接口,所以它即为此处动态代理的h对象

这里的JDK动态代理,首先使用SQLSession创建了一个触发管理对象MapperProxy,再使用该对象创建出代理对象,因此执行mapper代理对象方法是,首先会进入到触发管理类的invoke方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// MapperProxy.class
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);
}
}
...
}

可以发现没有直接调用方法,为什么没有直接调用?因为代理的是接口,没有逻辑实现

1
2
3
4
5
6
7
//MapperProxy.class
private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
...
// 方法被包装成一个 MapperMethod
return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
...
}

这里的处理分为两个步骤:

  1. 先对调用的接口方法进行了一次包装,包装成MapperMethod对象。
  2. 创建PlainMethodInvoker对象来执行封装后的方法。

分别进行分析:包装成MapperMethod

MapperMethod有两个成员变量:

  1. sqlCommand 记录了statement id (例如:com.panda.mapper.BlogMapper.selectBlogById) 和 SQL 类型
  2. 方法签名,主要是返回值的类型
1
2
3
4
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
this.command = new SqlCommand(config, mapperInterface, method);
this.method = new MethodSignature(config, mapperInterface, method);
}

跟进这两个构造函数:

1
2
3
4
5
6
7
8
9
10
11
12
public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
final String methodName = method.getName();
final Class<?> declaringClass = method.getDeclaringClass();
// 根据调用的接口类和方法名,查看配置对象中有没有对应的ms,这是mybatis的关键设计之一
MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
configuration);
...
// 找到对应的ms,利用ms给属性赋值
name = ms.getId();
type = ms.getSqlCommandType();
...
}

第二步method封装完成之后是调用了PlainMethodInvokerinvoke方法执行查询,跟进代码

1
2
3
4
5
//PlainMethodInvoker.class
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
// SQL执行的真正起点
return mapperMethod.execute(sqlSession, args);
}

MapperMethodexecute(sqlSession, args)方法就是sql执行的起点,注意只要查询还没执行,sqlsession就会一直往下传。


我们先来分析一个静态查询sql的执行:

1
2
3
4
5
6
7
case SELECT:
...
Object param = method.convertArgsToSqlCommandParam(args);
// 普通 select 语句的执行入口 >>
result = sqlSession.selectOne(command.getName(), param);
...
}

首先处理参数,调用convertArgsToSqlCommandParam(args)将调用传入的参数替换sql命令中的参数占位符,

在这之前,要介绍一下这里用到的参数处理器:ParamNameResolver,每次将mapper接口方法封装成一个MapperMethod的时候都生成一个此处理器,注意是一个接口方法生成一个,看构造器方法逻辑:

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
// ParamNameResolver构造器
public ParamNameResolver(Configuration config, Method method) {
final Class<?>[] paramTypes = method.getParameterTypes(); // 获取参数类型
final Annotation[][] paramAnnotations = method.getParameterAnnotations(); // 获取参数注解
final SortedMap<Integer, String> map = new TreeMap<>();
int paramCount = paramAnnotations.length;
// 该循环将从参数的@param注解中获取参数名
for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
if (isSpecialParameter(paramTypes[paramIndex])) {
// 跳过特殊参数,RowBound类型和ResultHandler类型的参数
continue;
}
String name = null;
for (Annotation annotation : paramAnnotations[paramIndex]) {
if (annotation instanceof Param) {
hasParamAnnotation = true;
name = ((Param) annotation).value();
break;
}
}
if (name == null) {
// @Param 未指定名称,则使用参数本来的名称
if (config.isUseActualParamName()) {
name = getActualParamName(method, paramIndex); // 获取到的名称为arg0...
}
if (name == null) {
// 至此还没有名字,是用参数位置作为名称
name = String.valueOf(map.size());
}
}
map.put(paramIndex, name);
}
names = Collections.unmodifiableSortedMap(map); // names 是一个map
}

总体逻辑是:

  • 对于每个方法参数,如果注解了@Param,则将该注解中的value指定为该参数的name,
  • 如果没有注解,判断是否配置了isUseActualParamName,配置为true的话,参数名称为”arg”+参数位置,从0开始,如第一个参数为‘arg0’
  • 如果该配置为false,则参数的名称为“参数位置”,如:0,1,2

最终得到一个参数位置与参数名称的map映射。

此时再来看convertArgsToSqlCommandParam方法

1
2
3
4
// MapperMethod.class
public Object convertArgsToSqlCommandParam(Object[] args) {
return paramNameResolver.getNamedParams(args);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//ParamNameResolver.class
public Object getNamedParams(Object[] args) {
final int paramCount = names.size();
if (args == null || paramCount == 0) {
return null;
} else if (!hasParamAnnotation && paramCount == 1) {
return args[names.firstKey()];
} else {
final Map<String, Object> param = new ParamMap<>();
int i = 0;
for (Map.Entry<Integer, String> entry : names.entrySet()) {
param.put(entry.getValue(), args[entry.getKey()]);
// add generic param names (param1, param2, ...)
final String genericParamName = GENERIC_NAME_PREFIX + (i + 1);
// ensure not to overwrite parameter named with @Param
if (!names.containsValue(genericParamName)) {
param.put(genericParamName, args[entry.getKey()]);
}
i++;
}
return param;
}
}

获取到参数之后,将调用selectOne

1
result = sqlSession.selectOne(command.getName(), param);
1
2
3
4
5
6
7
8
//DefaultSqlSession.class
public <T> T selectOne(String statement, Object parameter) {
List<T> list = this.selectList(statement, parameter);
if (list.size() == 1) {
return list.get(0);
}
...
}
1
2
3
public <E> List<E> selectList(String statement, Object parameter) {
return this.selectList(statement, parameter, RowBounds.DEFAULT);
}
1
2
3
4
5
6
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
...
}
1
2
3
4
5
6
7
8
//CachingExecutor.class
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
// 获取SQL
BoundSql boundSql = ms.getBoundSql(parameterObject);
// 创建CacheKey:什么样的SQL是同一条SQL? >>
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

到这里关键点又来了,两点:BoundSql的获取 和 CacheKey的生成

首先讲BoundSql,这是最后执行的sql命令的包装类,无论是敬爱sql还是动态sql,在生成BoundSql之前在内存中都只是以配置对象SqlSource的形式存在,SqlSource的数据结构是有层次关系的嵌套结构,在执行查询之前,需要将SqlSource中的各个节点拼接成一条sql语句。跟进源码:

1
2
3
4
5
//MappedStatement.class
public BoundSql getBoundSql(Object parameterObject) {
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
...
}
1
2
3
4
// StaticSqlSource.class
public BoundSql getBoundSql(Object parameterObject) {
return new BoundSql(configuration, sql, parameterMappings, parameterObject);
}

可以看到对于静态的sql,sql没有凭借需求,根据sqlSource就能直接创建出BoundSql返回。

再来看CacheKey:若两个查询拥有相同的CacheKey,我们就认为这是一条查询,开启缓存的情况下会先查询缓存。

那么CacheKey是怎么构成的,或者说,什么样的查询才能确定是同一个查询?

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

1
2
3
4
5
6
7
8
9
10
11
12
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
...
cacheKey.update(ms.getId()); // msId
cacheKey.update(rowBounds.getOffset()); // 翻页参数1
cacheKey.update(rowBounds.getLimit()); // 翻页参数2
cacheKey.update(boundSql.getSql());//sql语句
...
cacheKey.update(value); // 参数值
...
cacheKey.update(configuration.getEnvironment().getId());//数据源环境
return cacheKey;
}

也就是说,方法相同、翻页参数相同(2个)、sql语句相同、sql参数相同、数据源环境相同的情况下,才会被认为是同一个查询。

怎么比较两个CacheKey是否相同?如果一上来就比较6要素,效率不高,mybatis的做法是重写CacheKey类的hashCode方法和equel方法

让我们回到执行查询主流程上来,获取到boundSql 创建了 cacheKey 之后,调用了CacheExecutor的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
//CacheExecutor
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);
}

尝试获取缓存,未命中的话执行装饰器上层的BaseExecutorquery方法,完成之后写入二级缓存

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
//BaseExecutor
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--;
}
...
return list;
}

跟进queryFromDatabase

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//BaseExecutor
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;
}

看到了doQuery,终于要开始干活了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//SimpleExecutor
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);
}
}

到这里出现了Mybatis四大核心对象的第二个对象StatementHandler,这是一个接口,里面定义了操作jdbc对象StateMent的诸多方法,这里使用newStatementHandler创建了一个RoutingStatementHandler,使用了委托模式,内部持有一个StatementHandler引用,根据不同的配置该引用会指向不同StatementHandler的实现,而外层逻辑只需要调用RoutingStatementHandler`的方法就能将调用委托到具体的实现类中。

默认配置下,StatementHandler的真实实现类是PreparedStatementHandler

newStatementHandler

1
2
3
4
5
6
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;
}

newStatementHandler方法中创建好statementHandler之后,立即对其进行插件拦截操作。

接下来会调用prepareStatement方法创建具体的statement对象,此方法内部完成了statement对象的创建,参数的设置

PreparedStatementHandler的构造函数:

1
2
3
public PreparedStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
super(executor, mappedStatement, parameter, rowBounds, resultHandler, boundSql);
}

调用了父类的构造函数:

1
2
3
4
5
6
7
protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
...
// 创建了四大对象的其它两大对象 >>
// 创建这两大对象的时候分别做了什么?
this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
}

newParameterHandler 创建好parameterHandler之后立即进行插件逻辑植入。

1
2
3
4
5
6
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
// 植入插件逻辑(返回代理对象)
parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
return parameterHandler;
}

newResultSetHandler 创建好resultSetHandler之后立即进行插件逻辑植入。

1
2
3
4
5
6
7
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;
}

四大插件适用对象都拦截完成。