mybatis小册简单抄下

262 阅读25分钟

1.配置文件解析

1.1获取inputstream

InputStream xml = Resources.getResourceAsStream("mybatis-config-1.xml");

通过加载xml配置文件方式获取输入流,可以通过classloader的getResourceAsStream方法来加载,所以获取classloader就很关键,如下:

ClassLoader[] getClassLoaders(ClassLoader classLoader) {
 return new ClassLoader[]{classLoader,defaultClassLoader,
     Thread.currentThread().getContextClassLoader(),
     getClass().getClassLoader(),systemClassLoader};
}

1.2XPathParser的出现和作用

1.2.1XPathParser构造完成以下事情:

  • 在xPathParser中,通过XPathFactory.newInstance()构造出xpath用来解析xml值;
  • DocumentBuilderFactory.newInstance()构造出DocumentBuilderFactory对象,
  • 然后通过factory.newDocumentBuilder()得到DocumentBuilder对象
  • 最后通过DocumentBuilder.parse解析输入流inpustream获取到Document
  • 以上这些都是jdk中自带的完成XML加载,然后封装成mybatis的XpathParser

注意XMLConfigBuilder构造方法,里面会直接new Configuration(),这里面会typeAliasRegistry.registerAlias很多别名

public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}
public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {
commonConstructor(validation, variables, entityResolver);
this.document = createDocument(new InputSource(inputStream));
}
private Document createDocument(InputSource inputSource) {
// important: this must only be called AFTER common constructor
 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
 //调用factory 里面的一系列set方法,设置属性 此处省略.......
 DocumentBuilder builder = factory.newDocumentBuilder();
 //调用builder 里面的一系列set方法,设置属性 此处省略.......
 return builder.parse(inputSource);
}

1.2.2 xpathparser的作用与XNode

  • xPathParser主要是解析xml节点,通过它里面的xpath去执行的,xpath在它构造的时候实例化的;Xnode会传入variables进行解析节点里面的变量值
  • 如下常规解析节点方法
parseConfiguration(parser.evalNode("/configuration"));
public XNode evalNode(Object root, String expression) {
  Node node = (Node) evaluate(expression, root, XPathConstants.NODE);
  if (node == null) {
    return null;
  }
  return new XNode(this, node, variables);
}

public String evalString(Object root, String expression) {
  String result = (String) evaluate(expression, root, XPathConstants.STRING);
  result = PropertyParser.parse(result, variables);
  return result;
}
private Object evaluate(String expression, Object root, QName returnType) {
  try {
    return xpath.evaluate(expression, root, returnType);
  } catch (Exception e) {
    throw new BuilderException("Error evaluating XPath.  Cause: " + e, e);
  }
}

1.3parseConfiguration解析配置文件各个节点

有了前面的XPathParser和Xnode就可以解析配置文件的各个节点值了

  • 有涉及到包是如何被扫描的,然后注册成别名的; typeAliasesElement
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());

public Configuration parse() {
  if (parsed) {
    throw new BuilderException("Each XMLConfigBuilder can only be used once.");
  }
  parsed = true;
  parseConfiguration(parser.evalNode("/configuration"));
  return configuration;
}

private void XMLConfigBuilder#parseConfiguration(XNode root) {
  try {
    // issue #117 read properties first
    propertiesElement(root.evalNode("properties"));
    Properties settings = settingsAsProperties(root.evalNode("settings"));
    loadCustomVfs(settings);
    loadCustomLogImpl(settings);
    typeAliasesElement(root.evalNode("typeAliases"));
    pluginElement(root.evalNode("plugins"));
    objectFactoryElement(root.evalNode("objectFactory"));
    objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
    reflectorFactoryElement(root.evalNode("reflectorFactory"));
    settingsElement(settings);
    // read it after objectFactory and objectWrapperFactory issue #631
    environmentsElement(root.evalNode("environments"));
    databaseIdProviderElement(root.evalNode("databaseIdProvider"));
    typeHandlerElement(root.evalNode("typeHandlers"));
    mapperElement(root.evalNode("mappers"));
  } catch (Exception e) {
    throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
  }
}

1.3.1 mapper.xml文件解析

mapperElement(root.evalNode("mappers"));

mybatis-config.xml中配置mappers方式种类如下:

<mappers>
    <mapper resource="mapper/department.xml"/>
    <!--
    <mapper class="com.linkedbear.mybatis.mapper.UserMapper"/>
    <package name="com.linkedbear.mybatis.mapper"/>
    -->
</mappers>

1.3.1-1 resource属性方式的解析

通过构建XMLMapperBuilder(配置文件解析是构建XMLConfigBuilder),以mapper/department.xml作为resource构造输入流的方式,同样也是XPathParser,XPath,XNode的相互配合; 新出现一个MapperBuilderAssistant

private void mapperElement(XNode parent){
InputStream inputStream = Resources.getResourceAsStream(resource);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();

public void parse() {
  if (!configuration.isResourceLoaded(resource)) {
    configurationElement(parser.evalNode("/mapper"));
    configuration.addLoadedResource(resource);
    bindMapperForNamespace();
  }

  parsePendingResultMaps();
  parsePendingCacheRefs();
  parsePendingStatements();
}

public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
  this(new XPathParser(inputStream, true, configuration.getVariables(), new XMLMapperEntityResolver()),
      configuration, resource, sqlFragments);
}
private XMLMapperBuilder(XPathParser parser, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
  super(configuration);
  this.builderAssistant = new MapperBuilderAssistant(configuration, resource);
  this.parser = parser;
  this.sqlFragments = sqlFragments;
  this.resource = resource;
}

1.3.1-2 解析mapper.xml文件各个节点

private void configurationElement(XNode context) {
  try {
    String namespace = context.getStringAttribute("namespace");
    if (namespace == null || namespace.isEmpty()) {
      throw new BuilderException("Mapper's namespace cannot be empty");
    }
    builderAssistant.setCurrentNamespace(namespace);
    cacheRefElement(context.evalNode("cache-ref"));
    cacheElement(context.evalNode("cache"));
    parameterMapElement(context.evalNodes("/mapper/parameterMap"));
    resultMapElements(context.evalNodes("/mapper/resultMap"));
    sqlElement(context.evalNodes("/mapper/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);
  }
}
1.3.1-2.1 设置命名空间

XPathParser已经是获取到mapper.xml文件了,所以获取到它的Xnode节点,直接获取命名空间属性,这玩意要写,无所谓内容,但是不能没有

public void XMLMapperBuilder#parse() {
configurationElement(parser.evalNode("/mapper"));     }

void configurationElement(XNode context) {
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.isEmpty()) {
  throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
1.3.1-2.2 缓存解析
//XMLMapperBuilder#configurationElement
cacheRefElement(context.evalNode("cache-ref"));
cacheElement(context.evalNode("cache"));

1.获取二级缓存设置属性

private void cacheElement(XNode context) {
  if (context != null) {
    String type = context.getStringAttribute("type", "PERPETUAL");
    //省略N行.....
    Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
    boolean readWrite = !context.getBooleanAttribute("readOnly", false);
    builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
  }
}

2.构建缓存对象

  • 如果没有默认的缓存实现,则使用PerpetualCache,然后实例化它并为之设置属性,这属性是标签里面的子属性
  • 如果是PerpetualCache缓存,如果存在decorators,则依次实例化他们且将Cache作为参数传入并且设置属性,然后进行标准的装饰缓存设置
  • 如果是自定义的缓存实现,则套一层LoggingCache;非自定义缓存才会套装饰的那些缓存实现
public Cache build() {
  setDefaultImplementations();
  Cache cache = newBaseCacheInstance(implementation, id);
  setCacheProperties(cache);
  // issue #352, do not apply decorators to custom caches
  if (PerpetualCache.class.equals(cache.getClass())) {
    for (Class<? extends Cache> decorator : decorators) {
      cache = newCacheDecoratorInstance(decorator, cache);
      setCacheProperties(cache);
    }
    cache = setStandardDecorators(cache);//装饰者模式构建属性对应的缓存
  } else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {
    cache = new LoggingCache(cache); //直接封装成LoggingCache
  }
  return cache;
}

private Cache setStandardDecorators(Cache cache) {
  try {
    MetaObject metaCache = SystemMetaObject.forObject(cache);
    if (size != null && metaCache.hasSetter("size")) {
      metaCache.setValue("size", size);
    }
    if (clearInterval != null) {
      cache = new ScheduledCache(cache);
      ((ScheduledCache) cache).setClearInterval(clearInterval);
    }
    if (readWrite) {  //根据属性封装缓存实现
      cache = new SerializedCache(cache); //如果非只读,则进行序列化后放入缓存
    }
    cache = new LoggingCache(cache);
    cache = new SynchronizedCache(cache);
    if (blocking) {
      cache = new BlockingCache(cache);
    }
    return cache;
  } catch (Exception e) {
    throw new CacheException("Error building standard cache decorators.  Cause: " + e, e);
  }
}

最外层是BlockingCache-Synchronized-Logging-Serialized-Scheduled-LRU-Perpetaul image.png

1.3.1-2.3 resultMap解析

resultMap常规写法例子如下:

<resultMap id="userMap" type="com.linkedbear.mybatis.entity.User">
   <!-- <id property="id" column="id"/>-->
<constructor>
    <idArg column="id" javaType="String" name="idd" />
    <!--<arg column="name" javaType="String"/>-->
</constructor>

    <result property="birthday" column="birthday"/>
    <association property="department" javaType="com.linkedbear.mybatis.entity.Department">
        <id property="id" column="department_id"/>
        <result property="name" column="department_name"/>
    </association>
</resultMap>

resultMapElements(context.evalNodes("/mapper/resultMap"));

1.封装的目标类型提取

这个type可以是别名哦,所有源码底层如果在别名列表没查到,直接就用class.forname方式加载了

String type = resultMapNode.getStringAttribute("type",
    resultMapNode.getStringAttribute("ofType",
        resultMapNode.getStringAttribute("resultType",
            resultMapNode.getStringAttribute("javaType"))));
Class<?> typeClass = resolveClass(type);
if (typeClass == null) {
  typeClass = inheritEnclosingType(resultMapNode, enclosingType);
}
2.id 、result 、association 、collection 普通标签解析

拿到这些常规子节点的Xnode,就一顿解析这些标签上面的一些属性,比如name,property,column,javaType,jdbcType,select等等,最终封装成一个ResultMapping

private ResultMapping buildResultMappingFromContext(XNode context, Class<?> resultType, List<ResultFlag> flags) {
  String property;
  if (flags.contains(ResultFlag.CONSTRUCTOR)) {
    property = context.getStringAttribute("name");
  } else {
    property = context.getStringAttribute("property");
  }
  String column = context.getStringAttribute("column");
  String javaType = context.getStringAttribute("javaType");
  String jdbcType = context.getStringAttribute("jdbcType");
  //中间省略N多行.....
  String typeHandler = context.getStringAttribute("typeHandler");
  String resultSet = context.getStringAttribute("resultSet");
  Class<?> javaTypeClass = resolveClass(javaType);
  Class<? extends TypeHandler<?>> typeHandlerClass = resolveClass(typeHandler);
  JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
  return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect, nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resultSet, foreignColumn, lazy);
}
3.constructor标签解析
<constructor>
    <idArg column="id" javaType="String" name="idd" />
    <!--<arg column="name" javaType="String"/>-->
</constructor>

也是同样获取这个构造器的节点,最终也是调用buildResultMappingFromContext封装成一个ResultMapping,只是在它没有property属性,而是用name属性作为property

4.Discriminator的解析

这东西是用在根据列值的不同,映射不同的封装对象,不过它的封装使用builderAssistant.buildDiscriminator构造的是Discriminator

<resultMap id="userWithDiscriminator" type="com.linkedbear.mybatis.entity.User"> 
<discriminator column="deleted" javaType="boolean">
<case value="false" resultMap="userlazy"/> 
<case value="true" resultType="com.linkedbear.mybatis.entity.User"/> 
</discriminator>
</resultMap>

private Discriminator processDiscriminatorElement(XNode context, Class<?> resultType, List<ResultMapping> resultMappings) {
  String column = context.getStringAttribute("column");
  String javaType = context.getStringAttribute("javaType");
  String jdbcType = context.getStringAttribute("jdbcType");
  String typeHandler = context.getStringAttribute("typeHandler");
  Class<?> javaTypeClass = resolveClass(javaType);
  Class<? extends TypeHandler<?>> typeHandlerClass = resolveClass(typeHandler);
  JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
  Map<String, String> discriminatorMap = new HashMap<>();
  for (XNode caseChild : context.getChildren()) {
    String value = caseChild.getStringAttribute("value");
    String resultMap = caseChild.getStringAttribute("resultMap", processNestedResultMappings(caseChild, resultMappings, resultType));
    discriminatorMap.put(value, resultMap);
  }
  return builderAssistant.buildDiscriminator(resultType, column, javaTypeClass, jdbcTypeEnum, typeHandlerClass, discriminatorMap);
}
5.resultMap整体融合

继续解析继承过来的resultMap,融合前面解析好的resultMap和构造器以及Discriminator

String id = resultMapNode.getStringAttribute("id",
        resultMapNode.getValueBasedIdentifier());
String extend = resultMapNode.getStringAttribute("extends");
Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
try {
  return resultMapResolver.resolve();
} catch (IncompleteElementException e) {
  configuration.addIncompleteResultMap(resultMapResolver);
  throw e;
}
1.3.1-2.4 sql片段解析

这边会涉及到dataBaseId的区分

sqlElement(context.evalNodes("/mapper/sql")); 
    
private void sqlElement(List<XNode> list, String requiredDatabaseId) {
  for (XNode context : list) {
    String databaseId = context.getStringAttribute("databaseId");
    String id = context.getStringAttribute("id");
    id = builderAssistant.applyCurrentNamespace(id, false);
    if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) {
      sqlFragments.put(id, context);
    }
  }
}
1.3.1-2.5 CRUD的标签解析#XMLStatementBuilder
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));

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);
    }
  }
}
1.3.1-2.5-1 基础属性的解析获取
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);
1.3.1-2.5-2 include标签的解析
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());
1.3.1-2.5-3 自增主键的解析
processSelectKeyNodes(id, parameterTypeClass, langDriver);
1.3.1-2.5-4 SQL解析
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
//XMLLanguageDriver
@Override
public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
  XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
  return builder.parseScriptNode();
}
  • 解析CRUD标签内的节点进行封装为SqlNode呈现为SqlSource

解析节点中的内容是否为动态的SQL即是否有${}以及动态标签

public SqlSource parseScriptNode() {
  MixedSqlNode rootSqlNode = parseDynamicTags(context);
  SqlSource sqlSource;
  if (isDynamic) {
    sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
  } else {
    sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
  }
  return sqlSource;
}

protected MixedSqlNode parseDynamicTags(XNode node) {
  List<SqlNode> contents = new ArrayList<>();
  NodeList children = node.getNode().getChildNodes();
  for (int i = 0; i < children.getLength(); i++) {
    XNode child = node.newXNode(children.item(i));
    if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
      String data = child.getStringBody("");
      TextSqlNode textSqlNode = new TextSqlNode(data);
      if (textSqlNode.isDynamic()) {
        contents.add(textSqlNode);
        isDynamic = true;
      } else {
        contents.add(new StaticTextSqlNode(data));
      }
    } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
      String nodeName = child.getNode().getNodeName();
      NodeHandler handler = nodeHandlerMap.get(nodeName);
      if (handler == null) {
        throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
      }
      handler.handleNode(child, contents);
      isDynamic = true;
    }
  }
  return new MixedSqlNode(contents);
}
  • 如果不包含动态标签,会将SQL中的#变量,替换成?且封装对应的参数类型信息ParameterMappings(在prepareStatement中设置参数要用到),在RawSqlSource初始化的时候进行处理的,最终封装为了StaticSqlSource,里面的sql是可以直接用来给PrepareStatement执行的;所以非动态的SQL变量解析在解析阶段就已经处理完了;
public RawSqlSource(Configuration configuration, SqlNode rootSqlNode, Class<?> parameterType) {
  this(configuration, getSql(configuration, rootSqlNode), parameterType);
}

public RawSqlSource(Configuration configuration, String sql, Class<?> parameterType) {
  SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
  Class<?> clazz = parameterType == null ? Object.class : parameterType;
  sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap<>());
}
//SqlSourceBuilder
public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
  ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
  GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
  String sql;
  if (configuration.isShrinkWhitespacesInSql()) {
    sql = parser.parse(removeExtraWhitespaces(originalSql));
  } else {
    sql = parser.parse(originalSql);
  }
  return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
}

1.3.1-3 绑定Mapper接口给命名空间

xmlMapperBuilder.parse();

public void parse() {
  if (!configuration.isResourceLoaded(resource)) {
    configuration.addLoadedResource(resource);
    bindMapperForNamespace();
  }
}

private void bindMapperForNamespace() {
  String namespace = builderAssistant.getCurrentNamespace();
  if (namespace != null) {
    Class<?> boundType = null;
    try {
      boundType = Resources.classForName(namespace);
    } catch (ClassNotFoundException e) {
      // ignore, bound type is not required
    }
    if (boundType != null && !configuration.hasMapper(boundType)) {
      // Spring may not know the real resource name so we set a flag
      // to prevent loading again this resource from the mapper interface
      // look at MapperAnnotationBuilder#loadXmlResource
      configuration.addLoadedResource("namespace:" + namespace);
      configuration.addMapper(boundType);
    }
  }
}
1.3.1-3.1 构建mapper接口代理

如果mamper.xml里面的命名空间是个全路径类型,则会进行类型的加载,如1.3.1-3

public <T> void Configuration#addMapper(Class<T> type) {
  mapperRegistry.addMapper(type);
}

public <T> void addMapper(Class<T> type) {
  if (type.isInterface()) {
    if (hasMapper(type)) {
      throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
    }
    boolean loadCompleted = false;
    try {
      knownMappers.put(type, new MapperProxyFactory<>(type));
      MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
      parser.parse();
      loadCompleted = true;
    } finally {
      if (!loadCompleted) {
        knownMappers.remove(type);
      }
    }
  }
}
1.3.1-3.1.1.mapper接口代理创建
knownMappers.put(type, new MapperProxyFactory<>(type));

//MapperProxyFactory
public MapperProxyFactory(Class<T> mapperInterface) {
  this.mapperInterface = mapperInterface;
}
protected T newInstance(MapperProxy<T> mapperProxy) {
  return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
1.3.1-3.1.2.mapper接口执行的InvocationHandler

这边会根据接口方法是默认还是原本的,如果是原本的则是构建PlainMethodInvoker,最终执行的是MapperMethod类

public class MapperProxy<T> implements InvocationHandler, Serializable {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  try {
    if (Object.class.equals(method.getDeclaringClass())) {
      return method.invoke(this, args);
    } else {
      return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
    }
  } catch (Throwable t) {
    throw ExceptionUtil.unwrapThrowable(t);
  }
}

private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
  try {
    MapperMethodInvoker invoker = methodCache.get(method);
    if (invoker != null) {
      return invoker;
    }
    return methodCache.computeIfAbsent(method, m -> {
      if (m.isDefault()) {
        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 {
        return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
      }
    });
  } catch (RuntimeException re) {
    Throwable cause = re.getCause();
    throw cause == null ? re : cause;
  }
}

2.一级缓存和二级缓存原理

2.1一级缓存 基于BaseExecutor#localCache

一级缓存是基于SqlSession级别的,
因为 PerpetualCache localCache 是BaseExecutor类的成员变量,是作用在Executor上的,
而Executor是在每次opensession时候进行实例化的,所以只存在当前的 SqlSession 内有效,
不同的 SqlSession 内的localCache对象不同,
最后sqlSession.close的时候会去clearLocalCache的,所以一级缓存才不共享;

image.png

2.1.1 使用过程

查询数据库之前,先查询下一级缓存localCache是否有,有则直接返回

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());
  //省略N行......
  List<E> list;
  try {
    queryStack++;
    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--;
  }
  //省略N行......
  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;
}

2.2二级缓存基于namespace

2.2.1二级缓存实例化

二级缓存是在mapper.xml文件里面配置的cache标签,所以在解析mapper的时候,会进行设置到对应的MappedStatement,所以在currentNamespace下全局就一个cache对象,这个cache对象在查询的时候会被取出来,所以是夸相同命名空间下的sqlsession共享,而一级缓存使用的executor里面的cached对象

image.png

image.png

2.2.2查询数据前的过程

二级缓存的的使用,是在查询的时候,先查询二级缓存是否存在,通过TransactionalCacheManager.getObject去获取的,里面是委托TransactionalCache去执行的

public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
    throws SQLException {
  Cache cache = ms.getCache(); //这边取出来的是MappedStatement中设置的cache
if (cache != null) {
  flushCacheIfRequired(ms);
  if (ms.isUseCache() && resultHandler == null) {
    ensureNoOutParams(ms, boundSql);
    @SuppressWarnings("unchecked")
    List<E> list = (List<E>) tcm.getObject(cache, key);
 --------------------------------------------------------------------------   
public Object TransactionalCacheManager#getObject(Cache cache, CacheKey key) {
  return getTransactionalCache(cache).getObject(key);
}
 private TransactionalCache TransactionalCacheManager#getTransactionalCache(Cache cache) {
      return transactionalCaches.computeIfAbsent(cache, TransactionalCache::new);
} 
    
public Object TransactionalCache#getObject(Object key) {
  //委托查询的是使用cache对象即在mapperxml解析设置的那个cache
  Object object = delegate.getObject(key);
  if (object == null) {  //没查询到对象,则将key加入set集合
    entriesMissedInCache.add(key);
  }
  // issue #146
  if (clearOnCommit) {
    return null;
  } else {
    return object;
  }
}
 //TransactionalCache构造方法,最终是委托了cache对象去执行的
public TransactionalCache(Cache delegate) {
  this.delegate = delegate;
  this.clearOnCommit = false;
  this.entriesToAddOnCommit = new HashMap<>();
  this.entriesMissedInCache = new HashSet<>();
}
  

2.2.3查询数据后的过程

查询后,是将数据(key->data)放入TransactionalCache对象中的entriesToAddOnCommit(Map对象),在SqlSession没关闭前是没放入二级缓存中(cache)而是放在transactionCache的entriesToAddOnCommit中, 即用tcm.getObject是查询不到的

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); 
}
public void TransactionalCacheManager#putObject(Cache cache, CacheKey key, Object value) {
 getTransactionalCache(cache).putObject(key, value);
}

public void TransactionalCache#putObject(Object key, Object object) {
 entriesToAddOnCommit.put(key, object);
}

2.3Sqlsession.close的过程

2.3.1 开启二级缓存

sqlsession在关闭的时候,会进行mybatis的事务提交,然后会执行TransactionalCache中的commit,这个对象里面有个map类型的对象entriesToAddOnCommit,它存储的可是查询后的数据key为cachekey,value是数据。
因为二级缓存是夸SqlSession,所以在Sqlsession关闭的时候才进行事务提交,才会把当前sqlsession里面存储的数据存入到二级缓存对象中,防止中间有问题,把当前sqlsession有问题数据存入到缓存,造成其他人使用的时候出现脏数据;

//Sqlssion关闭
public void SqlSession#close() {
 try {
   executor.close(isCommitOrRollbackRequired(false));
   closeCursors();
   dirty = false;
 } finally {
   ErrorContext.instance().reset();
 }
}
//CachingExecutor#  
public void close(boolean forceRollback) {
 try {
   if (forceRollback) {
     tcm.rollback();
   } else {
     tcm.commit();//事务提交
   }
 } finally {
   delegate.close(forceRollback);
 }
}
//CachingExecutor.delegate ---> BaseExecutor    
public void close(boolean forceRollback) {
   try {
     rollback(forceRollback);
   } finally {
     if (transaction != null) {
       transaction.close();
     }
   }
}
//BaseExecutor
public void rollback(boolean required) throws SQLException {
 if (!closed) {
   try {
     clearLocalCache(); //清空一级缓存
     flushStatements(true);
   } finally {
     if (required) {
       transaction.rollback();
     }
   }
 }
}
//TransactionalCacheManager
public void commit() {
 for (TransactionalCache txCache : transactionalCaches.values()) {
   txCache.commit();
 }
}
//TransactionalCache
public void commit() {
 if (clearOnCommit) {
   delegate.clear();
 }
 flushPendingEntries();
 reset();
}
private void flushPendingEntries() {    //TransactionalCache
 for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) {
   delegate.putObject(entry.getKey(), entry.getValue());
 }
 for (Object entry : entriesMissedInCache) {
   if (!entriesToAddOnCommit.containsKey(entry)) {
     delegate.putObject(entry, null);
   }
 }
}

3.事务

3.1事务配置

<environments default="development">
   <environment id="development">
       <transactionManager type="JDBC"/>
       <dataSource type="POOLED">
           <property name="driver" value="com.mysql.jdbc.Driver"/>
           <property name="url" value="jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8"/>
           <property name="username" value="root"/>
           <property name="password" value="123456"/>
       </dataSource>
   </environment>
</environments>

3.2事务配置解析

environmentsElement(root.evalNode("environments"));
    
private void environmentsElement(XNode context) throws Exception {
  if (context != null) {
    if (environment == null) {
      environment = context.getStringAttribute("default");
    }
    for (XNode child : context.getChildren()) {
      String id = child.getStringAttribute("id");
      if (isSpecifiedEnvironment(id)) { //会区分不同的开发环境配置
        TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
        DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
        DataSource dataSource = dsFactory.getDataSource();
        Environment.Builder environmentBuilder = new Environment.Builder(id)
            .transactionFactory(txFactory)
            .dataSource(dataSource);
        configuration.setEnvironment(environmentBuilder.build());
      }
    }
  }
}
private TransactionFactory transactionManagerElement(XNode context) throws Exception {
  if (context != null) {
    String type = context.getStringAttribute("type");
    Properties props = context.getChildrenAsProperties();
    //直接无参构造实例化对象,用type从别名列表里面取值进行实例化
    TransactionFactory factory = (TransactionFactory) resolveClass(type).getDeclaredConstructor().newInstance();
    factory.setProperties(props);
    return factory;
  }
  throw new BuilderException("Environment declaration requires a TransactionFactory.");
}   

3.3事务使用的位置

3.3.1在openSession的时候会进行事务初始的设置,然后传入到Executor中

@Override
public SqlSession openSession() {
  return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
  Transaction tx = null;
  try {
    final Environment environment = configuration.getEnvironment();
    //这边获取的是根据type来创建的transaction对象
    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) {
    closeTransaction(tx); // may have fetched a connection so lets call close()
    throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
  } finally {
    ErrorContext.instance().reset();
  }
}
//传给了Eexcector
protected BaseExecutor(Configuration configuration, Transaction transaction) {
  this.transaction = transaction;
  this.deferredLoads = new ConcurrentLinkedQueue<>();
  this.localCache = new PerpetualCache("LocalCache");
  this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache");
  this.closed = false;
  this.configuration = configuration;
  this.wrapper = this;
}

3.3.2事务提交回滚

image.png

@Override
public void commit(boolean required) throws SQLException {
  if (closed) {
    throw new ExecutorException("Cannot commit, transaction is already closed");
  }
  clearLocalCache();
  flushStatements();
  if (required) {
    transaction.commit();
  }
}

@Override
public void rollback(boolean required) throws SQLException {
  if (!closed) {
    try {
      clearLocalCache();
      flushStatements(true);
    } finally {
      if (required) {
        transaction.rollback();
      }
    }
  }
}

4.Interceptor

4.1拦截器编写规范和配置

要编写一个插件需实现Interceptor接口的类,然后在类上要写上注解Intercepts,然后标注要拦截的类和方法,然后在config.xml文件中配置这个插件类,拦截器的处理的业务是编写在interceptor.intercept方法里面的

@Intercepts(@Signature(type = Executor.class, method = "query",
                       args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}))
public class CustomInterceptor implements Interceptor {

<plugins>
    <!--<plugin interceptor="com.linkedbear.mybatis.plugin.CustomInterceptor"/>-->
    <plugin interceptor="com.linkedbear.mybatis.plugin.PerformanceInterceptor">
        <!-- 最大容忍时间 -->
        <property name="maxTolerate" value="10"/>
    </plugin>
</plugins>

4.2配置解析

pluginElement(root.evalNode("plugins"));

private void pluginElement(XNode parent) throws Exception {
 if (parent != null) {
   for (XNode child : parent.getChildren()) {
     String interceptor = child.getStringAttribute("interceptor");
     Properties properties = child.getChildrenAsProperties();
     Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).getDeclaredConstructor().newInstance();
     interceptorInstance.setProperties(properties);
     configuration.addInterceptor(interceptorInstance);
   }
 }
} 

4.3作用位置

4.3.1添加配置的拦截器信息

一个是注解解析,一个是配置文件解析,进行解析插件,然后添加到interceptor
image.png

4.3.2mybatis自己的拦截切入点

mybatis这四个位置进行拦截包装,Executor,statementHandler,parameterHandler,resultSetHandler image.png

4.3.2.1 解析拦截器配置的注解参数

获取拦截器类上的注解Intercepts,然后获取里面的需要拦截的接口和方法,封装为声明的接口和方法集合对应关系

@Intercepts(@Signature(type = Executor.class, method = "query",
                       args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}))
public class CustomInterceptor implements Interceptor {
  ------------------------------------------------------------------------------------------  
private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
  Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
  // issue #251
  if (interceptsAnnotation == null) {
    throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());
  }
  Signature[] sigs = interceptsAnnotation.value();
  Map<Class<?>, Set<Method>> signatureMap = new HashMap<>();
  for (Signature sig : sigs) {
    Set<Method> methods = signatureMap.computeIfAbsent(sig.type(), k -> new HashSet<>());
    try {
      Method method = sig.type().getMethod(sig.method(), sig.args());
      methods.add(method);
    } catch (NoSuchMethodException e) {
      throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e);
    }
  }
  return signatureMap;
}

4.3.2.2 对配置的拦截接口创建jdk代理

Plugin.wrap(target, this);
  
public static Object wrap(Object target, Interceptor interceptor) {
 //获取当前拦截器上配置的拦截接口,以key为接口,value为方法集合的map返回
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
Class<?> type = target.getClass();
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
if (interfaces.length > 0) {
  return Proxy.newProxyInstance(type.getClassLoader(),interfaces,
      new Plugin(target, interceptor, signatureMap));
}
return target;
}
  
//动态代理执行的时候触发interceptor.intercept
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
  Set<Method> methods = signatureMap.get(method.getDeclaringClass());
  if (methods != null && methods.contains(method)) {
  //拦截器的核心业务逻辑位子
    return interceptor.intercept(new Invocation(target, method, args));
  }
  return method.invoke(target, args);
} catch (Exception e) {
  throw ExceptionUtil.unwrapThrowable(e);
}
}

4.4 分页插件pageHelper

关键类 PageInterceptor PageHelper

<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>5.3.2</version>
</dependency>

<plugins>
    <plugin interceptor="com.github.pagehelper.PageInterceptor">
        <property name="debug" value="true"/>
        <property name="helperDialect" value="mysql"/>
    </plugin>
</plugins>

4.4.1 拦截器声明

@Intercepts(
        {
                @Signature(type = Executor.class, method = "query",
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
                @Signature(type = Executor.class, method = "query",
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
        }
)
public class PageInterceptor implements Interceptor {

4.4.2 拦截器properties设置

@Override
public void setProperties(Properties properties) {
  //缓存 count ms
  msCountMap = CacheFactory.createCache(properties.getProperty("msCountCache"), "ms", properties);
  String dialectClass = properties.getProperty("dialect");
  if (StringUtil.isEmpty(dialectClass)) {
      dialectClass = default_dialect_class;
  }
  try {
  //加载方言的实现
      Class<?> aClass = Class.forName(dialectClass);
      dialect = (Dialect) aClass.newInstance();
  } catch (Exception e) {
      throw new PageException(e);
  }
//设置方言的属性
  dialect.setProperties(properties);

.....省略N行
}

4.4.2 拦截器invoke逻辑

@Override
public Object intercept(Invocation invocation) throws Throwable {
    try {
       //......省略获取参数
        checkDialectExists();
        //对 boundSql 的拦截处理
        if (dialect instanceof BoundSqlInterceptor.Chain) {
            boundSql = ((BoundSqlInterceptor.Chain) dialect).doBoundSql(BoundSqlInterceptor.Type.ORIGINAL, boundSql, cacheKey);
        }
        List resultList;
        //调用方法判断是否需要进行分页,如果不需要,直接返回结果
        if (!dialect.skip(ms, parameter, rowBounds)) {
            //开启debug时,输出触发当前分页执行时的PageHelper调用堆栈
            // 如果和当前调用堆栈不一致,说明在启用分页后没有消费,当前线程再次执行时消费,调用堆栈显示的方法使用不安全
            debugStackTraceLog();
            //判断是否需要进行 count 查询
            if (dialect.beforeCount(ms, parameter, rowBounds)) {
                //查询总数
                Long count = count(executor, ms, parameter, rowBounds, null, boundSql);
                //处理查询总数,返回 true 时继续分页查询,false 时直接返回
                if (!dialect.afterCount(count, parameter, rowBounds)) {
                    //当查询总数为 0 时,直接返回空的结果
                    return dialect.afterPage(new ArrayList(), parameter, rowBounds);
                }
            }
            resultList = ExecutorUtil.pageQuery(dialect, executor,
                    ms, parameter, rowBounds, resultHandler, boundSql, cacheKey);
        } else {
            //rowBounds用参数值,不使用分页插件处理时,仍然支持默认的内存分页
            resultList = executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);
        }
        return dialect.afterPage(resultList, parameter, rowBounds);
    } finally {
        if (dialect != null) {
            dialect.afterAll();
        }
    }
}

4.4.2.1 分页总数的count的实现

4.4.2.1-1 执行自定义的count实现
//获取count查询语句的id
String countMsId = countMsIdGen.genCountMsId(ms, parameter, boundSql, countSuffix);
//先判断是否存在手写的 count 查询
MappedStatement countMs = ExecutorUtil.getExistedMappedStatement(ms.getConfiguration(), countMsId);
if (countMs != null) {
    count = ExecutorUtil.executeManualCount(executor, countMs, parameter, boundSql, resultHandler);
//--------------------------------------------------------------------------------------
public static Long executeManualCount(Executor executor, MappedStatement countMs,
                                      Object parameter, BoundSql boundSql,
                                      ResultHandler resultHandler) throws SQLException {
    CacheKey countKey = executor.createCacheKey(countMs, parameter, RowBounds.DEFAULT, boundSql);
    BoundSql countBoundSql = countMs.getBoundSql(parameter);
    Object countResultList = executor.query(countMs, parameter, RowBounds.DEFAULT, resultHandler, countKey, countBoundSql);
    //某些数据(如 TDEngine)查询 count 无结果时返回 null
    if (countResultList == null || ((List) countResultList).isEmpty()) {
        return 0L;
    }
    return ((Number) ((List) countResultList).get(0)).longValue();
}
4.4.2.1-2 执行自动生成的count的mapperstatement执行

这边会根据不同的sql语句解析生成count的sql,通过countSqlParser.getSmartCountSql实现

countMs = MSUtils.newCountMappedStatement(ms, countMsId);//构建count的mappedStatement
count = ExecutorUtil.executeAutoCount(this.dialect, executor, countMs, parameter, boundSql, rowBounds, resultHandler);

public static Long executeAutoCount(Dialect dialect, Executor executor, MappedStatement countMs,
                                    Object parameter, BoundSql boundSql,
                                    RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    Map<String, Object> additionalParameters = getAdditionalParameter(boundSql);
    //创建 count 查询的缓存 key
    CacheKey countKey = executor.createCacheKey(countMs, parameter, RowBounds.DEFAULT, boundSql);
    //调用方言获取 count sql
    String countSql = dialect.getCountSql(countMs, boundSql, parameter, rowBounds, countKey);
    //countKey.update(countSql);
    BoundSql countBoundSql = new BoundSql(countMs.getConfiguration(), countSql, boundSql.getParameterMappings(), parameter);
    //当使用动态 SQL 时,可能会产生临时的参数,这些参数需要手动设置到新的 BoundSql 中
    for (String key : additionalParameters.keySet()) {
        countBoundSql.setAdditionalParameter(key, additionalParameters.get(key));
    }
    //对 boundSql 的拦截处理
    if (dialect instanceof BoundSqlInterceptor.Chain) {
        countBoundSql = ((BoundSqlInterceptor.Chain) dialect).doBoundSql(BoundSqlInterceptor.Type.COUNT_SQL, countBoundSql, countKey);
    }
    //执行 count 查询
    Object countResultList = executor.query(countMs, parameter, RowBounds.DEFAULT, resultHandler, countKey, countBoundSql);
    //某些数据(如 TDEngine)查询 count 无结果时返回 null
    if (countResultList == null || ((List) countResultList).isEmpty()) {
        return 0L;
    }
    return ((Number) ((List) countResultList).get(0)).longValue();
}

4.4.2.2 分页查询实现

通过对应的dialect实现获取对应的分页sql,然后重新构建BoundSql进行查询

resultList = ExecutorUtil.pageQuery(dialect, executor,
        ms, parameter, rowBounds, resultHandler, boundSql, cacheKey);

public static <E> List<E> pageQuery(Dialect dialect, Executor executor, MappedStatement ms, Object parameter,
                                    RowBounds rowBounds, ResultHandler resultHandler,
                                    BoundSql boundSql, CacheKey cacheKey) throws SQLException {
    //判断是否需要进行分页查询
    if (dialect.beforePage(ms, parameter, rowBounds)) {
        //生成分页的缓存 key
        CacheKey pageKey = cacheKey;
        //处理参数对象
        parameter = dialect.processParameterObject(ms, parameter, boundSql, pageKey);
        //调用方言获取分页 sql
        String pageSql = dialect.getPageSql(ms, boundSql, parameter, rowBounds, pageKey);
        BoundSql pageBoundSql = new BoundSql(ms.getConfiguration(), pageSql, boundSql.getParameterMappings(), parameter);

        Map<String, Object> additionalParameters = getAdditionalParameter(boundSql);
        //设置动态参数
        for (String key : additionalParameters.keySet()) {
            pageBoundSql.setAdditionalParameter(key, additionalParameters.get(key));
        }
        //对 boundSql 的拦截处理
        if (dialect instanceof BoundSqlInterceptor.Chain) {
            pageBoundSql = ((BoundSqlInterceptor.Chain) dialect).doBoundSql(BoundSqlInterceptor.Type.PAGE_SQL, pageBoundSql, pageKey);
        }
        //执行分页查询
        return executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, pageKey, pageBoundSql);
    } else {
        //不执行分页的情况下,也不执行内存分页
        return executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, cacheKey, boundSql);
    }
}

5.日志

5.1 配置

如果要指定具体的日志实现,则需要在配置文件中进行配置;默认在

   <settings>
<!--  <setting name="logImpl" value="LOG4J"/>-->
       <setting name="logImpl" value="STDOUT_LOGGING"/>
       <setting name="cacheEnabled" value="true"/>
   </settings>

5.2 配置解析

//XmlConfigBuilder    
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomLogImpl(settings);
    
private void loadCustomLogImpl(Properties props) {
  Class<? extends Log> logImpl = resolveClass(props.getProperty("logImpl"));//解析别名
  configuration.setLogImpl(logImpl);
}
//configuration
public void setLogImpl(Class<? extends Log> logImpl) {
  if (logImpl != null) {
    this.logImpl = logImpl;
    LogFactory.useCustomLogging(this.logImpl);
  }
}
//LogFactory
public static synchronized void useCustomLogging(Class<? extends Log> clazz) {
  setImplementation(clazz);
}
//LogFactory
private static void setImplementation(Class<? extends Log> implClass) {
    Constructor<? extends Log> candidate = implClass.getConstructor(String.class);
    Log log = candidate.newInstance(LogFactory.class.getName());
    logConstructor = candidate;
}
//默认加载以下存在的任何一个
static {
  tryImplementation(LogFactory::useSlf4jLogging);
  tryImplementation(LogFactory::useCommonsLogging);
  tryImplementation(LogFactory::useLog4J2Logging);
  tryImplementation(LogFactory::useLog4JLogging);
  tryImplementation(LogFactory::useJdkLogging);
  tryImplementation(LogFactory::useNoLogging);
}
//LogFactory
public static Log getLog(Class<?> clazz) {
  return getLog(clazz.getName());
}
//LogFactory
public static Log getLog(String logger) {
    return logConstructor.newInstance(logger);
}

5.3 其他日志增强类

5.3.1 ConnectionLogger#InvocationHandler

这是数据库连接的日志类,它是个inovcationHandler

public final class ConnectionLogger extends BaseJdbcLogger implements InvocationHandler

5.3.1-1 使用的位置

在获取数据库连接的时候,对connectoin接口进行代理的增加

protected Connection getConnection(Log statementLog) throws SQLException {
  Connection connection = transaction.getConnection();
  if (statementLog.isDebugEnabled()) {
    return ConnectionLogger.newInstance(connection, statementLog, queryStack);
  } else {
    return connection;
  }
}

public static Connection newInstance(Connection conn, Log statementLog, int queryStack) {
  InvocationHandler handler = new ConnectionLogger(conn, statementLog, queryStack);
  ClassLoader cl = Connection.class.getClassLoader();
  return (Connection) Proxy.newProxyInstance(cl, new Class[]{Connection.class}, handler);
}

5.3.1-2 代理核心逻辑

prepareStatement/prepareCall/createStatement方法执行的触发代理执行

@Override
public Object invoke(Object proxy, Method method, Object[] params)
    throws Throwable {
  try {
    if (Object.class.equals(method.getDeclaringClass())) {
      return method.invoke(this, params);
    }
    if ("prepareStatement".equals(method.getName()) || "prepareCall".equals(method.getName())) {
      if (isDebugEnabled()) {
        debug(" Preparing: " + removeExtraWhitespace((String) params[0]), true);
      }
      PreparedStatement stmt = (PreparedStatement) method.invoke(connection, params);
      stmt = PreparedStatementLogger.newInstance(stmt, statementLog, queryStack);
      return stmt;
    } else if ("createStatement".equals(method.getName())) {
      Statement stmt = (Statement) method.invoke(connection, params);
      stmt = StatementLogger.newInstance(stmt, statementLog, queryStack);
      return stmt;
    } else {
      return method.invoke(connection, params);
    }
  } catch (Throwable t) {
    throw ExceptionUtil.unwrapThrowable(t);
  }
}

5.3.2 PreparedStatementLogger#InvocationHandler

这个代理的接口是PreparedStatement,是在执行ConnectionLog#invoke初始化的,看5.3.1-2

public static PreparedStatement newInstance(PreparedStatement stmt, Log statementLog, int queryStack) {
 InvocationHandler handler = new PreparedStatementLogger(stmt, statementLog, queryStack);
 ClassLoader cl = PreparedStatement.class.getClassLoader();
 return (PreparedStatement) Proxy.newProxyInstance(cl, new Class[]{PreparedStatement.class, CallableStatement.class}, handler);
}

5.3.2-1 核心逻辑invoke

@Override
public Object invoke(Object proxy, Method method, Object[] params) throws Throwable {
  try {
    if (Object.class.equals(method.getDeclaringClass())) {
      return method.invoke(this, params);
    }
    if (EXECUTE_METHODS.contains(method.getName())) {
      if (isDebugEnabled()) {
        debug("Parameters: " + getParameterValueString(), true);
      }
      clearColumnInfo();
      if ("executeQuery".equals(method.getName())) {
        ResultSet rs = (ResultSet) method.invoke(statement, params);
        return rs == null ? null : ResultSetLogger.newInstance(rs, statementLog, queryStack);
      } else {
        return method.invoke(statement, params);
      }
    } else if (SET_METHODS.contains(method.getName())) {
      if ("setNull".equals(method.getName())) {
        setColumn(params[0], null);
      } else {
        setColumn(params[0], params[1]);
      }
      return method.invoke(statement, params);
    } else if ("getResultSet".equals(method.getName())) {
      ResultSet rs = (ResultSet) method.invoke(statement, params);
      return rs == null ? null : ResultSetLogger.newInstance(rs, statementLog, queryStack);
    } else if ("getUpdateCount".equals(method.getName())) {
      int updateCount = (Integer) method.invoke(statement, params);
      if (updateCount != -1) {
        debug("   Updates: " + updateCount, false);
      }
      return updateCount;
    } else {
      return method.invoke(statement, params);
    }
  } catch (Throwable t) {
    throw ExceptionUtil.unwrapThrowable(t);
  }
}

5.Mybatis整合spring

SqlSessionFactoryBean

5.1 SqlSessionFactory构建过程

@Override
public void afterPropertiesSet() throws Exception {
this.sqlSessionFactory = buildSqlSessionFactory();
}

5.1.1 Configuration构建

如果配置了configLocation参数,则通过XmlConfigBuilder方式加载inputStream,里面也肯定是直接new一个Configuration

final Configuration targetConfiguration;
XMLConfigBuilder xmlConfigBuilder = null;
if (this.configuration != null) {
  targetConfiguration = this.configuration;
  if (targetConfiguration.getVariables() == null) {
    targetConfiguration.setVariables(this.configurationProperties);
  } else if (this.configurationProperties != null) {
    targetConfiguration.getVariables().putAll(this.configurationProperties);
  }
} else if (this.configLocation != null) {
  xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
  targetConfiguration = xmlConfigBuilder.getConfiguration();
} else {
  targetConfiguration = new Configuration();
Optional.ofNullable(this.configurationProperties).ifPresent(targetConfiguration::setVariables);
}

5.1.2 扫描别名注解的包,注册别名类

if (hasLength(this.typeAliasesPackage)) {
  scanClasses(this.typeAliasesPackage, this.typeAliasesSuperType).stream()
      .filter(clazz -> !clazz.isAnonymousClass()).filter(clazz -> !clazz.isInterface())
      .filter(clazz -> !clazz.isMemberClass()).forEach(targetConfiguration.getTypeAliasRegistry()::registerAlias);
}

if (!isEmpty(this.typeAliases)) {
  Stream.of(this.typeAliases).forEach(typeAlias -> {
    targetConfiguration.getTypeAliasRegistry().registerAlias(typeAlias);
    LOGGER.debug(() -> "Registered type alias: '" + typeAlias + "'");
  });
}

5.1.3 处理插件、类型处理器

if (!isEmpty(this.plugins)) {
  Stream.of(this.plugins).forEach(plugin -> {
    targetConfiguration.addInterceptor(plugin);
    LOGGER.debug(() -> "Registered plugin: '" + plugin + "'");
  });
}

if (hasLength(this.typeHandlersPackage)) {
  scanClasses(this.typeHandlersPackage, TypeHandler.class).stream().filter(clazz -> !clazz.isAnonymousClass())
      .filter(clazz -> !clazz.isInterface()).filter(clazz -> !Modifier.isAbstract(clazz.getModifiers()))
      .forEach(targetConfiguration.getTypeHandlerRegistry()::register);
}

if (!isEmpty(this.typeHandlers)) {
  Stream.of(this.typeHandlers).forEach(typeHandler -> {
    targetConfiguration.getTypeHandlerRegistry().register(typeHandler);
    LOGGER.debug(() -> "Registered type handler: '" + typeHandler + "'");
  });
}

targetConfiguration.setDefaultEnumTypeHandler(defaultEnumTypeHandler);

5.1.4 添加缓存

跟解析缓存标签</Cache>后,将缓存添加到configuration中类似    
Optional.ofNullable(this.cache).ifPresent(targetConfiguration::addCache);

5.1.5 解析mybatis的配置文件

如果配置了configlocation,则就会构建XmlConfigBuilder,就要解析配置文件了

xmlConfigBuilder.parse();

5.1.5 处理数据源和事务工厂

在解析mybatis全局文件的时候,事务工厂是通过type指定进行加载的,如果没配置,则这边使用SpringManagedTransactionFactory

targetConfiguration.setEnvironment(new Environment(this.environment,
    this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory,
    this.dataSource));

5.1.6 xxxMaper.xml文件加载解析

mapperLocations是Resource对象,那就只能配置classpath:mapper/*.xml形式了

private Resource[] mapperLocations;
    
for (Resource mapperLocation : this.mapperLocations) {
        XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
            targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
          xmlMapperBuilder.parse();
    }
  }
}

5.1.7 构建DefaultSqlSessionFactory

new SqlSessionFactoryBuilder().build(targetConfiguration)

public SqlSessionFactory build(Configuration config) {
  return new DefaultSqlSessionFactory(config);
}

5.2 Mapper接口注入SpringIOC

在没有整合spring的时候,我们获取mapper对象是通过Sqlsession.getMapper,底层是从mapperRegistry拿的,它是在解析xxxMapper.xml文件时候进行addMapper进去的。
如果整合了spring,获取mapper是从容器拿的,所以得有个东西把mapper接口给扫描出来注入到IOC中

5.2.1 Mapper接口扫描(MapperScannerConfigurer)

public class MapperScannerConfigurer
    implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {

@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
  if (this.processPropertyPlaceHolders) {
    processPropertyPlaceHolders();
  }

  ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
  scanner.setAddToConfig(this.addToConfig);
  scanner.setAnnotationClass(this.annotationClass);
  scanner.setMarkerInterface(this.markerInterface);
  scanner.setSqlSessionFactory(this.sqlSessionFactory);
  scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
  scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
  scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
  scanner.setResourceLoader(this.applicationContext);
  scanner.setBeanNameGenerator(this.nameGenerator);
  scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
  if (StringUtils.hasText(lazyInitialization)) {
    scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));
  }
  scanner.registerFilters();
  scanner.scan(
      StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}

5.2.1-1 获取basePackage包下的类转BeanDefinition

protected Set<BeanDefinitionHolder> ClassPathBeanDefinitionScanner#doScan(String... basePackages) {
   Assert.notEmpty(basePackages, "At least one base package must be specified");
   Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
   for (String basePackage : basePackages) {
      Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
      for (BeanDefinition candidate : candidates) {
         ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
         candidate.setScope(scopeMetadata.getScopeName());
         String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
         if (candidate instanceof AbstractBeanDefinition) {
            postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
         }
         if (candidate instanceof AnnotatedBeanDefinition) {
            AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
         }
         if (checkCandidate(beanName, candidate)) {
            BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
            definitionHolder =
                  AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
            beanDefinitions.add(definitionHolder);
            registerBeanDefinition(definitionHolder, this.registry);
         }
      }
   }
   return beanDefinitions;
}

5.2.1-2 解析BeanDefination

private void ClassPathMapperScanner#processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
  GenericBeanDefinition definition;
  for (BeanDefinitionHolder holder : beanDefinitions) {
    definition = (GenericBeanDefinition) holder.getBeanDefinition();
    String beanClassName = definition.getBeanClassName();
    // the mapper interface is the original class of the bean
    // but, the actual class of the bean is MapperFactoryBean
    definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
    definition.setBeanClass(this.mapperFactoryBeanClass);
    definition.getPropertyValues().add("addToConfig", this.addToConfig);
    boolean explicitFactoryUsed = false;
    if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
      definition.getPropertyValues().add("sqlSessionFactory",
          new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
      explicitFactoryUsed = true;
    } else if (this.sqlSessionFactory != null) {
      definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
      explicitFactoryUsed = true;
    }

    if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
      if (explicitFactoryUsed) {
      definition.getPropertyValues().add("sqlSessionTemplate",
          new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
      explicitFactoryUsed = true;
    } else if (this.sqlSessionTemplate != null) {
      if (explicitFactoryUsed) {
      definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
      explicitFactoryUsed = true;
    }

    if (!explicitFactoryUsed) 
      definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
    }
    definition.setLazyInit(lazyInitialization);
  }
}
5.2.1-2.1 设置beanDefination的beanClass成mapperFactoryBeanClass
  • mapperFactoryBeanClass构造参数是mapperInterface,所以要将beanName设置到构造参数进去,在MapperFactoryBean进行实例化的时候会设置这个参数,这样mapperInterface就进来
  • 在beanDefination中加入sqlsessionFactory对象,这样实例化的时候也会设置到mapperFactoryBean里面
// the mapper interface is the original class of the bean
// but, the actual class of the bean is MapperFactoryBean
definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
definition.setBeanClass(this.mapperFactoryBeanClass);

if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
  definition.getPropertyValues().add("sqlSessionFactory",
      new RuntimeBeanReference(this.sqlSessionFactoryBeanName));

5.2.2 MapperFactoryBean初始化加载mapper接口到configuration中

MapperFactoryBean继承了DaoSupport,它实现了InitializingBean接口,在afterProperties方法中进行了mapperInterface的加载

public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T>
public abstract class SqlSessionDaoSupport extends DaoSupport
public abstract class DaoSupport implements InitializingBean{
   @Override
   public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException {
      // Let abstract subclasses check their configuration.
      checkDaoConfig();
//-------------------------------------------------
@Override
protected void MapperFactoryBean#checkDaoConfig() {
  Configuration configuration = getSqlSession().getConfiguration();
  if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
      configuration.addMapper(this.mapperInterface);
  }
}

5.3 从IOC容器获取Mapper对象

ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("spring-mybatis.xml");
DepartmentMapper departmentMapper = ctx.getBean(DepartmentMapper.class);

5.3.1 MapperFactoryBean取mapper对象

因为mapper接口在被spring扫描的时候,都转成了MapperFactoryBean了(5.2.1-2.1),它是个FactoryBean,所以获取对象走就是getObject

@Override
public T MapperFactoryBean#getObject() throws Exception {
  return getSqlSession().getMapper(this.mapperInterface);
}

55.Mybatis-plus整合spring(待续)

使用plus,如果只是CRUD,你只要定义Mapper接口,不用定义对应的xml文件就能实现功能;
它对mybaits的关键类都进行了继承,如:
MybatisSqlSessionFactoryBean、MybatisConfiguration、MybatisMapperRegistry、 MybatisSqlSessionFactoryBuilder、MybatisMapperProxyFactory

55.1 SqlSessionFactory构建过程

plus在构建SqlSeesionFactory过程,只是把mybatis的一些关键类替换成自己的定义

6.mybatis整体流程

6.1 全局配置文件加载

6.1.1 classLoader加载文件

classLoaderWrapper.getResourceAsStream(resource, loader);

6.1.2 解析配置文件#XMLConfigBuilder

XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);

public Configuration parse() {
 if (parsed) {
   throw new BuilderException("Each XMLConfigBuilder can only be used once.");
 }
 parsed = true;
 parseConfiguration(parser.evalNode("/configuration"));
 return configuration;
}

6.2 SqlSession的执行

6.2.1 sqlsession创建

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) {
   closeTransaction(tx); // may have fetched a connection so lets call close()
   throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
 } finally {
   ErrorContext.instance().reset();
 }
}

6.2.1-1 构建事务Transaction

在全局解析配置文件的时候,构建enviroment对象的时候设置了transactionFactory,所以通过它可以获取到,再构建Transaction

6.2.1-2 构建Executor并增强

默认的cacheEnabled是true,可以在set标签里面设置这个属性;这个是控制数据可以存入二级缓存,从这边看,如果你没有在mapper.xml里面开启二级缓存,这个也可以设置为false
最后会对Executor进行增加插件的增强

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 {
 executor = new SimpleExecutor(this, transaction);
}
if (cacheEnabled) {
 executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}

6.2.1-3 构建SqlSession

有了configuration和Executor对象,就可以构建sqlsession对象了,在具体进行CRUD的时候,都是委托给executor去执行的

return new DefaultSqlSession(configuration, executor, autoCommit);

6.3 Mapper的代理执行

在解析Mapper.xml文件的时候,XMLMapperBuilder会将命名空间的类型加入到knownMappers,加入的是MapperProxyFactory类型,在获取Sqlsession获取mapper的时候,获取就是mapperProxyFactory类型

knownMappers.put(type, new MapperProxyFactory<>(type));

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
  throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
  return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
  throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}

protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}

public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}

6.3.1 Mapper的执行CRUD先走代理mapperProxy#invoke

  • 对Mapper接口进行了jdk代理,执行的invokerHandler逻辑就是MapperProxy,所以当你的mapper这样调用时userMapper.getUesrName(),就进入了MapperProxy类的invoke方法了
  • 在MapperProxy#invoke里面又包装了一层PlainMethodInvoker和MapperMethod
  • 所以最终会进入MapperMethod这个类,执行它的excute方法
  • mapperProxy#invoke --> PlainMethodInvoker#invoke-->MapperMethod#execute
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
//MapperProxy
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  if (Object.class.equals(method.getDeclaringClass())) {
    return method.invoke(this, args);
  } else {
    return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
  }
}

private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
try {
  MapperMethodInvoker invoker = methodCache.get(method);
  if (invoker != null) {
    return invoker;
  }
  return methodCache.computeIfAbsent(method, m -> {
    if (m.isDefault()) {
      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 {
      return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
    }
  });
} catch (RuntimeException re) {
  Throwable cause = re.getCause();
  throw cause == null ? re : cause;
}
}

6.4 Mapper接口方法属性解析

6.4.1 返回值和参数名解析

  • 在MapperMethod对象构建的时候,会解析当前调用方法method返回值和参数相关属性,如返回值可以是void,collection,optional,Cursor,参数类型也可以是RowBounds,ResultHandler
  • 参数名的解析默认是按照0->arg0,1->arg1来对应的;如果有写@param参数注解则是名称对应

image.png

image.png

public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
this.command = new SqlCommand(config, mapperInterface, method);
//参数类型,返回值解析
this.method = new MethodSignature(config, mapperInterface, method);
}

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();
this.returnsCursor = Cursor.class.equals(this.returnType);
this.returnsOptional = Optional.class.equals(this.returnType);
this.mapKey = getMapKey(method);
this.returnsMap = this.mapKey != null;
this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
//参数解析
this.paramNameResolver = new ParamNameResolver(configuration, method);
}

6.4.1.1 mapper方法参数名解析封装

public interface UserMapper {
   List<User> findAll( String id,String name);

执行上面的方法后,会最终进入MapperMethod类的execute方法,在执行CRUD的之前都会有一个参数的提取封装,要么返回一个Map,要么参数值

Object param = MethodSignature.convertArgsToSqlCommandParam(args);
6.4.1.1-1 参数多个且无@Param注解
List<User> findAll(List<String> id, String name);

image.png

6.4.1.1-2 参数多个且有@Param注解
List<User> findAll(@Param("id") String id, @Param("name")String name);

image.png

6.4.1.1-3 参数多个且有@Param注解且刚好是param1,param2
List<User> findAll(@Param("param1") String id, @Param("param2")String name);

image.png

6.4.1.1-4 参数一个且无@Param注解
  • 如果无注解且单个参数集合或者数组类型,则是如此形式

image.png image.png

  • 无注解且非集合数组类型,则直接返回对应的值
  • 参数有注解,则直接走多个参数的处理逻辑

6.4 获取BoundSql

BoundSql的获取是通过SqlSource去获取的,SqlSource的封装在解析Sql时候已经构建了(看1.3.1-2.5-4),分为DynamicSqlSource和RawSqlSource(StaticSqlSource)

6.4.1 StaticSqlSource获取BoundSql

直接封装BoundSql

public class StaticSqlSource implements SqlSource {
  @Override
  public BoundSql getBoundSql(Object parameterObject) {
    return new BoundSql(configuration, sql, parameterMappings, parameterObject);
  }

6.4.2 DynamicSqlSource获取BoundSql

6.4.2.1 执行封装好的SqlNode

在SQL解析阶段,会对动态SQL标签解析为对应的SqlNode,如TrimSqlNode,IfSqlNode. 在方法执行阶段就直接将这些封装好的SqlNode进行执行,这边也会对#{}变量进行替换?以及解析变量名相关信息包括指定了jdbctype,与StaticSqlSource那边的解析一致

public class DynamicSqlSource implements SqlSource {

  @Override
  public BoundSql getBoundSql(Object parameterObject) {
    DynamicContext context = new DynamicContext(configuration, parameterObject);
    //执行封装好的sqlNode
    rootSqlNode.apply(context);
    SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
    Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
    SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
    context.getBindings().forEach(boundSql::setAdditionalParameter);
    return boundSql;
  }

6.5 创建缓存的key

CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);

6.6 StatementHandler的构建

public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
  Statement stmt = null;
  try {
    Configuration configuration = ms.getConfiguration();
    StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
    stmt = prepareStatement(handler, ms.getStatementLog());
    return handler.query(stmt, resultHandler);
  } finally {
    closeStatement(stmt);
  }
}

6.6.1 构建统一的RoutingStatementHandler并插件代理

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

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

6.6.2 StatementHandler的处理

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

6.6.2-1 获取数据库连接

protected Connection getConnection(Log statementLog) throws SQLException {
 Connection connection = transaction.getConnection();
 if (statementLog.isDebugEnabled()) {
   return ConnectionLogger.newInstance(connection, statementLog, queryStack);
 } else {
   return connection;
 }
}

6.6.2-2 handler准备

6.6.2-2.1实例化handler
 connection.prepareStatement(sql);
6.6.2-2.2设置handler查询时间
stmt.setQueryTimeout(queryTimeout);
6.6.2-2.3setFetchSize
stmt.setFetchSize(fetchSize);

6.6.2-3 handler的参数化设置

获取boundSql中的parametersMapping参数进行一一设置对应的值和类型

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

6.7 结果集封装

6.7.1 封装ResultSet获取字段信息

获取到对应的字段名,字段对应的JDBC类型和Java类型

ResultSetWrapper rsw = getFirstResultSet(stmt);
                                                
public ResultSetWrapper(ResultSet rs, Configuration configuration) throws SQLException {
 super();
 this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
 this.resultSet = rs;
 final ResultSetMetaData metaData = rs.getMetaData();
 final int columnCount = metaData.getColumnCount();
 for (int i = 1; i <= columnCount; i++) {
   columnNames.add(configuration.isUseColumnLabel() ? metaData.getColumnLabel(i) : metaData.getColumnName(i));
   jdbcTypes.add(JdbcType.forCode(metaData.getColumnType(i)));
   classNames.add(metaData.getColumnClassName(i));
 }
}

6.7.2 处理单个结果集映射即非嵌套映射

private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
    throws SQLException {
  DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
  ResultSet resultSet = rsw.getResultSet();
  skipRows(resultSet, rowBounds);
  while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
    ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
    Object rowValue = getRowValue(rsw, discriminatedResultMap, null);
    storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
  }
}

6.7.2.1 创建实体空对象

Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix);

6.7.2.1 设置在resultMap中未指定的属性值

找出在resultMap未进行声明映射的属性,通过反射设置值,映射列名来自resultSetWrapper里面的封装好的列名集合

private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
//为声明的列名映射属性
  List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);
  boolean foundValues = false;
  if (!autoMapping.isEmpty()) {
    for (UnMappedColumnAutoMapping mapping : autoMapping) {
      final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);
      if (value != null) {
        foundValues = true;
      }
      if (value != null || (configuration.isCallSettersOnNulls() && !mapping.primitive)) {
        // gcode issue #377, call setter on nulls (value is not 'found')
        metaObject.setValue(mapping.property, value);
      }
    }
  }
  return foundValues;
}

//找出在resultMapp中声明和未声明的属性集合
private void loadMappedAndUnmappedColumnNames(ResultMap resultMap, String columnPrefix) throws SQLException {
  List<String> mappedColumnNames = new ArrayList<>();
  List<String> unmappedColumnNames = new ArrayList<>();
  final String upperColumnPrefix = columnPrefix == null ? null : columnPrefix.toUpperCase(Locale.ENGLISH);
  final Set<String> mappedColumns = prependPrefixes(resultMap.getMappedColumns(), upperColumnPrefix);
  for (String columnName : columnNames) {
    final String upperColumnName = columnName.toUpperCase(Locale.ENGLISH);
    if (mappedColumns.contains(upperColumnName)) {
      mappedColumnNames.add(upperColumnName);
    } else {
      unmappedColumnNames.add(columnName);
    }
  }
  mappedColumnNamesMap.put(getMapKey(resultMap, columnPrefix), mappedColumnNames);
  unMappedColumnNamesMap.put(getMapKey(resultMap, columnPrefix), unmappedColumnNames);
}

6.7.2.2 设置在resultMap中指定的属性值

private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, ResultLoaderMap lazyLoader, String columnPrefix)
    throws SQLException {
  final List<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
  boolean foundValues = false;
  final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
  for (ResultMapping propertyMapping : propertyMappings) {
    String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
    if (propertyMapping.getNestedResultMapId() != null) {
      // the user added a column attribute to a nested result map, ignore it
      column = null;
    }
    if (propertyMapping.isCompositeResult()
        || (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH)))
        || propertyMapping.getResultSet() != null) {
      Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix);
      // issue #541 make property optional
      final String property = propertyMapping.getProperty();
      if (property == null) {
        continue;
      } else if (value == DEFERRED) {
        foundValues = true;
        continue;
      }
      if (value != null) {
        foundValues = true;
      }
      if (value != null || (configuration.isCallSettersOnNulls() && !metaObject.getSetterType(property).isPrimitive())) {
        // gcode issue #377, call setter on nulls (value is not 'found')
        metaObject.setValue(property, value);
      }
    }
  }
  return foundValues;
}

6.7.2.3 将解析好的一行数据存储到DefaultResultHandler

storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);

private void storeObject(ResultHandler<?> resultHandler, DefaultResultContext<Object> resultContext, Object rowValue, ResultMapping parentMapping, ResultSet rs) throws SQLException {
  if (parentMapping != null) {
    linkToParents(rs, parentMapping, rowValue);
  } else {
    callResultHandler(resultHandler, resultContext, rowValue);
  }
}

@SuppressWarnings("unchecked" /* because ResultHandler<?> is always ResultHandler<Object>*/)
private void callResultHandler(ResultHandler<?> resultHandler, DefaultResultContext<Object> resultContext, Object rowValue) {
  resultContext.nextResultObject(rowValue);
  ((ResultHandler<Object>) resultHandler).handleResult(resultContext);
}

@Override
public void handleResult(ResultContext<?> context) {
  list.add(context.getResultObject());
}