前言

  前面两章我们构建了SqlSessionFactory,并通过SqlSessionFactory创建了我们需要的SqlSession,并通过这个SqlSession获取了我们需要的代理mapper。而SqlSession中最重要的则是用来处理请求的Executor,在上一章中我们创建了SimpleExecutor,并使用CachingExecutor代理了一下,我们最终得到了CachingEecutor.本章我们主要研究代理mapper的执行过程。这里再贴一下之前的测试代码

@Autowired
private SqlSessionFactory sqlSessionFactory; @GetMapping("/get")
public List<AssetInfo> get(){
SqlSession sqlSession = sqlSessionFactory.openSession();
AssetInfoMapper mapper = sqlSession.getMapper(AssetInfoMapper.class);
List<AssetInfo> test = mapper.get("测试删除" , "123123123");
System.out.println(test);
return test;
}
public interface AssetInfoMapper {
List<AssetInfo> get(@Param("name") String name, @Param("id")String id);
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.mapper.AssetInfoMapper">
<select id="get" resultType="com.entity.AssetInfo">
select * from asset_info where id =#{id} and `name` = #{name}
</select>
</mapper>

正文

1.MethodProxy

  我们知道代理类最终执行的是我们实现了InvocationHandler接口里面的方法,而我们的创建mapper代理类传入的参数是MethodProxy,所以最终执行的也是这个类里面的invoke方法

//构建的SqlSession
private final SqlSession sqlSession;
//本mapper对应的接口
private final Class<T> mapperInterface;
//方法缓存 里面存储的是方法的签名识别信息等
private final Map<Method, MapperMethod> methodCache; @Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
//判断下是不是代理的Object类
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}
//如果是接口中的default方法
else if (method.isDefault()) {
if (privateLookupInMethod == null) {
return invokeDefaultMethodJava8(proxy, method, args);
} else {
return invokeDefaultMethodJava9(proxy, method, args);
}
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
//如果是普通方法 我们主要分析这儿 这儿的主要目的是将方法的信息存起来
final MapperMethod mapperMethod = cachedMapperMethod(method);
//执行其方法
return mapperMethod.execute(sqlSession, args);
}
//设置方法标志签名
private MapperMethod cachedMapperMethod(Method method) {
return methodCache.computeIfAbsent(method,
k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}

  上面的invoke方法中做了一些判断,这儿我们主要分析下常规的方法调用。这儿有两步,第一步是创建我们调用的方法的签名mapperMethod并存储,第二部是调用mapperMethod的execute即执行方法。所以我们先看下是如何创建的方法签名。根据代码可以知道  如果method对应的mapperMethod不存在就调用其有参构造创建一个,所以我们主要看有参构造

public class MapperMethod {

  private final SqlCommand command;
private final MethodSignature method; public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
//创建该方法的command
this.command = new SqlCommand(config, mapperInterface, method);
//创建该方法的MethodSignature
this.method = new MethodSignature(config, mapperInterface, method);
} ...........
}

  通过上面可以看到,该方法主要创建了两个属性,SqlCommand,MethodSignature。我可以得知后面的execute方法肯定也是围绕这两个属性展开的。我们逐个分析就可以知道它们的作用了

  SqlCommand

public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
//获得方法名 例如get
final String methodName = method.getName();
//获得方法声明的接口类型
final Class<?> declaringClass = method.getDeclaringClass();
//根据这个获取mybatis初始化时为xml中的每个方法构建的MappedStatement 里面主要存储该sql的信息
MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
configuration);
//如果未找到
if (ms == null) {
//查看是否为有 @Flush注解
if (method.getAnnotation(Flush.class) != null) {
name = null;
//说明该方法只是用来刷新的
type = SqlCommandType.FLUSH;
} else {
throw new BindingException("Invalid bound statement (not found): "
+ mapperInterface.getName() + "." + methodName);
}
} else {
//获取到该 MappedStatement的id 一般为接口名+'.'+方法名
name = ms.getId();
//获取到方法类型 select|update |delete
type = ms.getSqlCommandType();
//如果未知直接抛异常
if (type == SqlCommandType.UNKNOWN) {
throw new BindingException("Unknown execution method for: " + name);
}
}
} private MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName,
Class<?> declaringClass, Configuration configuration) {
//获取到该 MappedStatement的id 一般为接口名+'.'+方法名
String statementId = mapperInterface.getName() + "." + methodName;
//如果有旧直接返回
if (configuration.hasStatement(statementId)) {
return configuration.getMappedStatement(statementId);
}
//如果没有 但我们传入的接口和方法声明的接口是一个接口 就返回null
else if (mapperInterface.equals(declaringClass)) {
return null;
}
//否则有可能是我们的自己的接口继承了mapper的接口,所以找到其父接口(如果有)继续递归调用
for (Class<?> superInterface : mapperInterface.getInterfaces()) {
if (declaringClass.isAssignableFrom(superInterface)) {
MappedStatement ms = resolveMappedStatement(superInterface, methodName,
declaringClass, configuration);
if (ms != null) {
return ms;
}
}
}
return null;
}

  通过上述代码可以得知SqlCommand主要存储了该方法的类型 | update |delete |select ,以及方法的全名,即声明的接口名+方法名,可能有同学发现了其信息来自于MappedStatment,而这个是根据我们构造的方法全名查到的。其实在第一张解析mapper.xml的过程即介绍xmlMapperBuilder中有提到在构建过程中最后一步为mapper中的每个sql方法构建了一个MappedStatment。这个类将会在后面频繁的用到,所以我们详细讲一下,希望大家仔细看。当时没有详细的讲构建过程,这儿就顺带说一下,我们回顾下那个方法

private void configurationElement(XNode context) {
try {
//获取该mapper的namespace的值 一般为接口地址
String namespace = context.getStringAttribute("namespace");
//判空
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
//设置当前处理的namespace
builderAssistant.setCurrentNamespace(namespace);
//设置当前nameSpace的缓存引用
cacheRefElement(context.evalNode("cache-ref"));
//设置当前nameSpace的缓存
cacheElement(context.evalNode("cache"));
//设置当前nameSpace的所有parameterMap
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
//设置当前nameSpace的所有resultMap
resultMapElements(context.evalNodes("/mapper/resultMap"));
//设置当前nameSpace的所有sql语句
sqlElement(context.evalNodes("/mapper/sql"));
//设置当前nameSpace 每个sql方法的方法类型
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
}
}

  最后一步的buildStatementFromContext方法即是构建方法

private void buildStatementFromContext(List<XNode> list) {
if (configuration.getDatabaseId() != null) {
buildStatementFromContext(list, configuration.getDatabaseId());
}
buildStatementFromContext(list, null);
}
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
for (XNode context : list) {
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
configuration.addIncompleteStatement(statementParser);
}
}
}

  上面又是很熟悉的创建构建器,然后调用构建器的parsexx方法,我们还是主要看parseStatementNode方法。  注意  该方法中的XNode属性就已经是我们每个方法的sql了。

public void parseStatementNode() {
//获取到sql方法定义的id 一般对应接口中的方法名
String id = context.getStringAttribute("id");
//获取到sql方法定义的数据库id 多数据源时会用到
String databaseId = context.getStringAttribute("databaseId");
//如果这儿判断下如果有指定id 那当前数据源是否为指定id
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}
//获取到该sql方法的名字即类型 |select | delete |update |insert
String nodeName = context.getNode().getNodeName();
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
//设置是查询操作还是更新操作
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
//是否刷新缓存
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
//是否使用二级缓存
boolean useCache = context.getBooleanAttribute("useCache", isSelect);
//返回结果是否排序
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false); // 解析sql方法中包含的节点 例如<if></if>等
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());
//获得参数类型
String parameterType = context.getStringAttribute("parameterType");
//获得参数类型对应的class信息
Class<?> parameterTypeClass = resolveClass(parameterType);
//获取语言
String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang); //暂时不明确这句的作用
processSelectKeyNodes(id, parameterTypeClass, langDriver); // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
KeyGenerator keyGenerator;
// id +'!selectKey'
String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
//namespace + id + '!selectKey'
keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true); //获取到该方法的key即主键生成器
if (configuration.hasKeyGenerator(keyStatementId)) {
keyGenerator = configuration.getKeyGenerator(keyStatementId);
} else {
keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
}
//解析sql语句 这里面会将sql语句解析出来 并将#{xx} 替换为 ? 占位符
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
//获得statementType 默认为PREPARED 即预编译类型
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
//影响期望值
Integer fetchSize = context.getIntAttribute("fetchSize");
//超时时间
Integer timeout = context.getIntAttribute("timeout");
//自定义的parameterMap
String parameterMap = context.getStringAttribute("parameterMap");
//返回类型
String resultType = context.getStringAttribute("resultType");
//返回类型的class属性
Class<?> resultTypeClass = resolveClass(resultType);
//自定义的返回resultMap
String resultMap = context.getStringAttribute("resultMap");
//自定义的resultSetType
String resultSetType = context.getStringAttribute("resultSetType");
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
if (resultSetTypeEnum == null) {
resultSetTypeEnum = configuration.getDefaultResultSetType();
}
//标记唯一属性
String keyProperty = context.getStringAttribute("keyProperty");
//生成的键值 设置列名
String keyColumn = context.getStringAttribute("keyColumn");
//多结果集时使用
String resultSets = context.getStringAttribute("resultSets");
//构建MappedStatement
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}

  这个方法就是把sql方法所有的元素解析出来,然后使用builderAssistant.addMappedStatement方法把参数全部传进去  然后构建值,我们接着看

  public MappedStatement addMappedStatement() {

    if (unresolvedCacheRef) {
throw new IncompleteElementException("Cache-ref not yet resolved");
}
//获取到该MappedStatement的id 这儿为namespace+'.'+id
id = applyCurrentNamespace(id, false);
//获得方法SqlCommandType
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
//构建者模式传入参数
MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
.resource(resource)
.fetchSize(fetchSize)
.timeout(timeout)
.statementType(statementType)
.keyGenerator(keyGenerator)
.keyProperty(keyProperty)
.keyColumn(keyColumn)
.databaseId(databaseId)
.lang(lang)
.resultOrdered(resultOrdered)
.resultSets(resultSets)
.resultMaps(getStatementResultMaps(resultMap, resultType, id))
.resultSetType(resultSetType)
.flushCacheRequired(valueOrDefault(flushCache, !isSelect))
.useCache(valueOrDefault(useCache, isSelect))
.cache(currentCache); ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
if (statementParameterMap != null) {
//添加ParameterMap
statementBuilder.parameterMap(statementParameterMap);
} MappedStatement statement = statementBuilder.build();
//添加
configuration.addMappedStatement(statement);
return statement;
} protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection")
.conflictMessageProducer((savedValue, targetValue) ->
". please check " + savedValue.getResource() + " and " + targetValue.getResource());
public void addMappedStatement(MappedStatement ms) {
mappedStatements.put(ms.getId(), ms);
}

  这个方法就获取到mappedStatment的id以及使用构建者模式构建系列参数,最终创建好后存储到Configuration中的mappedStatements 存储,key则为mappedStatment的id

  到此 mappedStatment就创建好了并且成功存储

  分析完了SqlCommand顺带分析了mappedStatment后我们接着分析MapperMethod中另一个属性 即MethodSignature的创建过程

  MethodSignature

public MethodSignature(Configuration configuration, Class<?> mapperInterface, Method method) {
//该方法返回类型
Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface);
if (resolvedReturnType instanceof Class<?>) {
this.returnType = (Class<?>) resolvedReturnType;
} else if (resolvedReturnType instanceof ParameterizedType) {
this.returnType = (Class<?>) ((ParameterizedType) resolvedReturnType).getRawType();
} else {
this.returnType = method.getReturnType();
}
//是否返回空
this.returnsVoid = void.class.equals(this.returnType);
//是否返回多个数据
this.returnsMany = configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray();
//是否返回的是Cursor类型
this.returnsCursor = Cursor.class.equals(this.returnType);
//是否返回的是Optional类型
this.returnsOptional = Optional.class.equals(this.returnType);
//获取到mapKey 即@MapKey值
this.mapKey = getMapKey(method);
//是否返回map
this.returnsMap = this.mapKey != null;
//参数中是否有RowBounds 即mybatis的分页工具类
this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
//是否有ResultHandler类
this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
//获取到参数值 即@Param的值,如果有则为里面的value值 如果没有 则为'0','1','2'之类的
this.paramNameResolver = new ParamNameResolver(configuration, method);
}

  这个比较容易理解,就是解析了下该方法的参数信息之类的。

  到此mapperMethod就创建完毕了。接下来就是执行其execute方法了

2.MapperMethod.execute()

  该方法就是我们创建好mapperMethod后的重头戏方法了,也是我们调用代理类后最终要调用的方法

public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
//查看方法类型
switch (command.getType()) {
//插入
case INSERT: {
//构建参数
Object param = method.convertArgsToSqlCommandParam(args);
//执行
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:
//如果返回为void 又有结果处理类 那就走这个方法
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
//返回多条数据
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
//返回map
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
//返回多条数据Cursor
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
}
//返回单条普通数据
else {
Object param = method.convertArgsToSqlCommandParam(args);
//查询单条数据
result = sqlSession.selectOne(command.getName(), param);
//如果返回Optional则包装一下
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());
}
//如果返回null 而接收值时初始化数据类型 并且接收值不为void 那就抛异常
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;
}

  本案例中中是select方法,并返回list,所以我们解析executeForMany(sqlSession, args);  其他查询操作大同小异,至于更新操作本文最后会讲解到

3.executeForMany(sqlSession, args);

  这儿主要是参数转换一下,然后调用SqlSession中的selectList方法  主要传入sqlCommand的id ,包装参数,分页信息。可见最终还是交由我们的SqlSession去执行了。所以又回到了我们上一章构建的DefaultSqlSession中去查询了

private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
List<E> result;
//参数转换一下
Object param = method.convertArgsToSqlCommandParam(args);
//如果方法有分页类RowBounds
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
//分页查询
result = sqlSession.selectList(command.getName(), param, rowBounds);
} else {
//非分页查询
result = sqlSession.selectList(command.getName(), param);
}
// 由于结果都是Object 所以这儿需要转换下 增加数组支持
if (!method.getReturnType().isAssignableFrom(result.getClass())) {
if (method.getReturnType().isArray()) {
return convertToArray(result);
} else {
return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
}
}
return result;
}

4.DefaultSqlSession.selectList()

  这儿我们通过statementId拿到了我们上面构建的MappedStatement ,也就是标红了让大家注意看的地方。然后调用执行器查询,由于默认打开了二级缓存,所以根据上一章的步骤,这儿使用的是CachingExecutor,并且里面封装了SimpleExecutor

@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
//根据statementId拿到我们构建的MappedStatement 里面包含了该方法的所有xml解析出来的信息
MappedStatement ms = configuration.getMappedStatement(statement);
//使用执行器 即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();
}
}

5.CachingExecutor.query()

  

  @Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
//拿到sql内容以及有关信息
BoundSql boundSql = ms.getBoundSql(parameterObject);
//得到该方法缓存的key 这个key涉及到mybatis的一二级缓存 ,我们下一章讲缓存的时候将会着重讲
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
//继续执行重载方法
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

  上面的代码主要通过我们的参数拿到对应的BoundSql,里面包含了sql信息,参数信息等。然后通过一系列的参数创建了一个缓存key,这个在mybatis的缓存中起着非常重要的作用。我们下一章讲一二级缓存时将会着重讲。现在我们接着看重载方法

  

//这儿是SimpleExecutor
private final Executor delegate;
private final TransactionalCacheManager tcm = new TransactionalCacheManager(); @Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
//获取到该方法所在mapper的二级缓存
Cache cache = ms.getCache();
//如果缓存不为null
if (cache != null) {
//如果该方法需要刷新缓存则刷新二级缓存
flushCacheIfRequired(ms);
//如果该方法使用缓存 且没有结果处理器
if (ms.isUseCache() && resultHandler == null) {
//确定下参数类型
ensureNoOutParams(ms, boundSql);
@SuppressWarnings("unchecked")
//根据缓存获取数据
List<E> list = (List<E>) tcm.getObject(cache, key);
//如果结果为空 则会调用实际的SimpleExecutor的query方法
//所以如果真实结果为空,那么二级缓存会失效
if (list == null) {
list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
//二级缓存存入进去
tcm.putObject(cache, key, list); // issue #578 and #116
}
//返回结果
return list;
}
}
//SimpleExecutor的query方法
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

  这个方法则体现出了该代理执行器的作用,就是为了二级缓存。我们先接着看具体的查询方法即deletegate.query。这儿的delegate则是我们传入的SimpleExecutor。

6.BaseExecutor.query()方法

  我们看下SimpleExecutor的结构,其继承了抽象类BaseExecutor。我们用的query方法则是在BaseExecutor中。

  

@SuppressWarnings("unchecked")
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
//检查该Executor即SqlSession是否关闭。
if (closed) {
throw new ExecutorException("Executor was closed.");
}
//如果查询的栈深度为0 并且该方法有刷新标志
if (queryStack == 0 && ms.isFlushCacheRequired()) {
//清除本地缓存 即一级缓存
clearLocalCache();
}
List<E> list;
try {
//查询栈深度+1
queryStack++;
//一级缓存查找结果 并且需要结果处理器为空
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
//不为null 但是该select为存储过程的话 执行下存储过程
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
//缓存为空,或者有结果处理器 则从数据库拿 并且数据存入一级缓存中
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
//查询栈深度-1
queryStack--;
}
//如果查询栈深度为0
if (queryStack == 0) {
//将队列中的 延迟重新加载任务加载
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
//清除队列
deferredLoads.clear();
//如果缓存配置是STATEMENT 即一次查询过程 那这个时候清空缓存
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}

  该方法则主要涉及到了一级缓存,由此可以看出二级缓存优先度高于一级缓存。我们接着看从数据库查询数据的逻辑。

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 {
//执行查询
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执行,doQuery是BaseExecutor的一个抽象方法,所以具体的实现是在SimpleExecutor中

7.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
Configuration configuration = ms.getConfiguration();
//生成一个声明处理器
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
//根据这个声明处理器和日志实现类型 来获取jdbc的处理器
stmt = prepareStatement(handler, ms.getStatementLog());
//调用我们创建的预编译处理器执行查询方法
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}

  里面创建了两个比较重要的东西,声明处理器和jdbc标准的sql处理器。 我们分别看两个的创建逻辑,先看StatementHandler 

 public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
//创建一个RoutingStatementHandler
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
//执行一次拦截器的方法
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return 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());
} }

    通过上面的结果不难发现,虽然我们创建的是RoutingStatementHandler,但是通过其构造函数发现,这个statment只是一个路由的作用,最终的处理器肯定还是根据MappedStatement.getStatementType()决定的。这儿有三个类型从上到下分别是普通处理器,预编译处理器,存储过程处理器。这儿我们使用的则是预编译处理器,也是mybatis默认

    还有一个需要注意的地方是这儿又执行了一个拦截器的方法,第一次是在我们创建SqlSession时构建Executor的地方执行过一次。并且都是执行的plugin方法

  接下来我们看生成jdbc标准的处理器的逻辑prepareStatement(handler, ms.getStatementLog());

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
//获取到jdbc连接 这儿是通过的SpringDataSourceManager
Connection connection = getConnection(statementLog);
//预编译 最终由BaseStatementHandler执行 (PreparedStatementHandler未实现该方法)
stmt = handler.prepare(connection, transaction.getTimeout());
//设置参数 最终由PreparedStatementHandler中的parameterHandler执行
handler.parameterize(stmt);
return stmt;
}

  这段代码相信熟悉jdbc的同学都了解,是在进行java的原生sql连接步骤,即加载驱动获取连接,预编译,设置参数,还有执行,获取结果与释放连接,由于本案例使用的是连接池,所以我们主要关注后面的执行方法了。前面三个步骤也不细讲了,感兴趣的可以去了解下jdbc原生开发步骤即可

8.PreparedStatementHandler.query方法

  由于RoutingStatementHandler最终会选择到我们的预执行处理器,所以最终处理器的也就是PreparedStatementHandler.query方法。我们就来分析这个方法

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

  在7中我们已经执行了jdbc中前几个步骤,到这儿直接执行execute方法,jdbc会将sql发送到数据库上,我们则只需要获取返回结果即可,到这儿其实我们的sql就已经执行完毕了,接下来需要做的就是处理返回结果。在看结果前我们先这个resultSetHandler是如何生成的。在该类的构造函数中有这样的逻辑

this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);

  可见这个结果处理器是根据我们这个方法的一系列标识来创建的。

  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;
}

  创建了一个默认的结果处理器,并且第三次执行了拦截器的plugin方法。我们接着看最后的方法,即结果封装

9.DefaultResultSetHandler.handleResultSets()

@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;
//获取到第一个返回结果并包装返回
ResultSetWrapper rsw = getFirstResultSet(stmt);
//获取到所有的resultMap
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
//获取到ResultMap的数量
int resultMapCount = resultMaps.size();
//验证下有效的数量 即 不能小于1
validateResultMapsCount(rsw, resultMapCount);
//
while (rsw != null && resultMapCount > resultSetCount) {
//获取到当前的resultMap
ResultMap resultMap = resultMaps.get(resultSetCount);
//将结果存入multipleResults
handleResultSet(rsw, resultMap, multipleResults, null);
//获取下一个结果
rsw = getNextResultSet(stmt);
//清除下缓存
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
//如果方法多个结果集的情况
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);
}

  上面则是做了一些判断,然后根据我们定义的ResultMap,或者ResultType 和ResultSet分情况进行封装,由于具体的结果集封装过程非常的长以及篇幅有限,本系列文主要讲解mybatis的原理和流程,所以对具体的封装过程就不详细分析了。

 

10  有关更新操作 

  由于delete,update,insert都属于更新操作,最后都会调用相同的方法,所以这儿一并讲解,其实更新操作相比查询操作去掉了结果封装的一步,增加了清空缓存,以及标记修改的功能。其他的步骤一致,所以我只分析几个特别的地方。

  10.1 DefaultSqlSession.update   增加修改操作标记

  @Override
public int update(String statement, Object parameter) {
try {
//本次SqlSession已出现过修改操作
dirty = true;
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.update(ms, wrapCollection(parameter));
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}

  10.2CachingExecutor.update()清空二级缓存

  @Override
public int update(MappedStatement ms, Object parameterObject) throws SQLException {
flushCacheIfRequired(ms);
return delegate.update(ms, parameterObject);
} private void flushCacheIfRequired(MappedStatement ms) {
Cache cache = ms.getCache();
//更新操作isFlushCacheRequired 即sql方法中的flushCache默认标记为true
if (cache != null && ms.isFlushCacheRequired()) {
//清空二级缓存
tcm.clear(cache);
}
}

  

  10.3BaseExecutor.update  清空一级缓存

  @Override
public int update(MappedStatement ms, Object parameter) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
//清除一级缓存
clearLocalCache();
return doUpdate(ms, parameter);
} @Override
public void clearLocalCache() {
if (!closed) {
localCache.clear();
localOutputParameterCache.clear();
}
}

  特殊步骤已做说明,其他的和查询操作一致,相信查询操作理解了修改操作很快也就能理解。不过由于我们创建的DefaultSqlSession的autocommit默认为false,所以有更新操作,需要手动提交事务。我们可以看下事务代码即SqlSession.commit()方法

  

  @Override
public void commit(boolean force) {
try {
//判断下是否有改动 或者强制提交 然后再commit
executor.commit(isCommitOrRollbackRequired(force));
dirty = false;
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error committing transaction. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}

  提交后本次SqlSession的修改操作又置为false,使用Executor进行提交

  @Override
public void commit(boolean required) throws SQLException {
//清空缓存并提交事务
delegate.commit(required);
//清空二级缓存
tcm.commit();
}

  我们看最后调用的是BaseExecutor.commit()方法

  @Override
public void commit(boolean required) throws SQLException {
if (closed) {
throw new ExecutorException("Cannot commit, transaction is already closed");
}
//清空一级缓存
clearLocalCache();
//刷新
flushStatements();
//如果本次操作有必要提交(有改动 或者自动提交设置为true) 或者强制提交再提交事务
if (required) {
//我们使用的是SpringManagedTransaction 进行事务提交
transaction.commit();
}
}

  

  系统会先判断是否有必要提交(有改动 或者自动提交设置为true) 或者强制提交再提交事务,

  

  

  

完结

  现在我们已经成功的通过代理类的方法调用SqlSession中的查找方法,而SqlSession在拿到方法对应的MappedStatment后交由其Executor执行具体的查询方法,中间会涉及到拦截器已即缓存,这个我会在后面专门的章节分析。

  提供一张我整理的查询操作的图作为流程思路参考,有完整的从代理类开始调用的逻辑。修改操作原理类

  

  

  

  

mybatis源码探索笔记-3(使用代理mapper执行方法)的更多相关文章

  1. mybatis源码探索笔记-2(构建SqlSession并获取代理mapper)

    前言 上篇笔记我们成功的装载了Configuration,并写入了我们全部需要的信息.根据这个Configuration创建了DefaultSqlSessionFactory.本篇我们实现构建SqlS ...

  2. mybatis源码探索笔记-5(拦截器)

    前言 mybatis中拦截器主要用来拦截我们在发起数据库请求中的关键步骤.其原理也是基于代理模式,自定义拦截器时要实现Interceptor接口,并且要对实现类进行标注,声明是对哪种组件的指定方法进行 ...

  3. mybatis源码探索笔记-1(构建SqlSessionFactory)

    前言 mybatis是目前进行java开发 dao层较为流行的框架,其较为轻量级的特性,避免了类似hibernate的重量级封装.同时将sql的查询与与实现分离,实现了sql的解耦.学习成本较hibe ...

  4. mybatis源码探索笔记-4(缓存原理)

    前言 mybatis的缓存大家都知道分为一级和二级缓存,一级缓存系统默认使用,二级缓存默认开启,但具体用的时候需要我们自己手动配置.我们依旧还是先看一个demo.这儿只贴出关键代码 public in ...

  5. mybatis源码学习:基于动态代理实现查询全过程

    前文传送门: mybatis源码学习:从SqlSessionFactory到代理对象的生成 mybatis源码学习:一级缓存和二级缓存分析 下面这条语句,将会调用代理对象的方法,并执行查询过程,我们一 ...

  6. Mybatis源码详解系列(三)--从Mapper接口开始看Mybatis的执行逻辑

    简介 Mybatis 是一个持久层框架,它对 JDBC 进行了高级封装,使我们的代码中不会出现任何的 JDBC 代码,另外,它还通过 xml 或注解的方式将 sql 从 DAO/Repository ...

  7. async-validator 源码学习笔记(六):validate 方法

    系列文章: 1.async-validator 源码学习(一):文档翻译 2.async-validator 源码学习笔记(二):目录结构 3.async-validator 源码学习笔记(三):ru ...

  8. mybatis源码分析(四)---------------代理对象的生成

    在mybatis两种开发方式这边文章中,我们提到了Mapper动态代理开发这种方式,现在抛出一个问题:通过sqlSession.getMapper(XXXMapper.class)来获取代理对象的过程 ...

  9. mybatis源码分析(五)------------SQL的执行过程

    在对SQL的执行过程进行分析前,先看下测试demo: /** * @author chenyk * @date 2018年8月20日 */ public class GoodsDaoTest { pr ...

随机推荐

  1. 关于static 关键字的总结

    转发自:https://www.cnblogs.com/xrq730/p/4820992.html 前言 之前讲到final关键字的作用是每次面试的时候我必问求职者的两个问题之一,另外一个问题就是文本 ...

  2. Service Worker,Web Worker,WebSocket的对比

    Service Worker 处理网络请求的后台服务.适用于离线和后台同步数据或推送信息.不能直接和dom交互.通过postMessage方法交互. Web Worker 模拟多线程,允许复杂计算功能 ...

  3. python接口自动化测试 - requests库的post请求进行文件上传

    前言 如果需要发送文件到服务器,比如上传图片.视频等,就需要发送二进制数据. 一般上传文件使用的都是 Content-Type: multipart/form-data; 数据类型,可以发送文件,也可 ...

  4. 【网易官方】极客战记(codecombat)攻略-地牢-橱柜里的骷髅

    关卡连接: https://codecombat.163.com/play/level/cupboards-of-kithgard 谁知道什么样的恐怖事情潜伏在 Kithgard 的橱柜里? 简介: ...

  5. VS Code 配置 C/C++ (Windwos)

    下载VSCode https://code.visualstudio.com/Download 在扩展里安装C/C++插件 配置MinGW 需要再环境变量中的path中添加MinGW的bin目录 修改 ...

  6. 微信小程序UDP通信,注意点 接收 onMessage 收到的message是ArrayBuffer缓冲,不能直接输出,要另转String处理

    1.WXML 页面代码 <!--index.wxml--> <view class="container"> <view class="us ...

  7. 大小端(MSB & LSB)

    谈到字节序的问题,必然牵涉到两大CPU派系.那就是Motorola的PowerPC系列CPU和Intel的x86系列CPU.PowerPC系列采用big endian方式存储数据,而x86系列则采用l ...

  8. Linux list_head

    在linux kernel里面链表应用非常广泛. 我们在应用程序中,定义一个链表结构通常要包含数据域,如下: typedef struct _listNode{ int data; struct _l ...

  9. MySQL删除语句比较,清空表数据,重置自增长索引

    drop truncate delete 程度从强到弱 1.drop table tbdrop将表格直接删除,没有办法找回 2.truncate (table) tbtruncate 删除表中的所有数 ...

  10. 深入delphi编程理解之消息(一)WINDOWS原生窗口编写及消息处理过程

    通过以sdk方式编制windows窗口程序,对理解windows消息驱动机制和delphi消息编程有很大的帮助. sdk编制windows窗口程序的步骤: 1.对TWndClass对象进行赋值; 2. ...