mybatis源码探索笔记-3(使用代理mapper执行方法)
前言
前面两章我们构建了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执行具体的查询方法,中间会涉及到拦截器已即缓存,这个我会在后面专门的章节分析。
提供一张我整理的查询操作的图作为流程思路参考,有完整的从代理类开始调用的逻辑。修改操作原理类