mybatis源码之创建SqlSessionFactory代码分析 - mapper xml解析

本文将通过阅读mybatis源码的方式了解mapper xml配置文件的解析过程、MappedStatement的创建过程以及openSession的过程。

如有错漏,请评论指正,水平有限,不喜勿喷。

系列文档:


解析mapper xml文件

入口

private void parseConfiguration(XNode root) {
  try {
    // ...
    // 解析主配置文件的mappers节点
    mapperElement(root.evalNode("mappers"));
  } catch (Exception e) {
    throw new BuilderException("Error parsing SQL Mapper Configuration. Cause ...");
  }
}

mapperElement方法:

private void mapperElement(XNode parent) throws Exception {
  if (parent != null) {
    // 遍历mappers下面的所有子节点
    for (XNode child : parent.getChildren()) {
      // <package ...>节点才进入这个分支
      if ("package".equals(child.getName())) {
        String mapperPackage = child.getStringAttribute("name");
        configuration.addMappers(mapperPackage);
      } else {
        // <mapper ...>节点进入这个分支
        String resource = child.getStringAttribute("resource");
        String url = child.getStringAttribute("url");
        String mapperClass = child.getStringAttribute("class");
        // 按照我们的配置进入第一个分支
        if (resource != null && url == null && mapperClass == null) {
          ErrorContext.instance().resource(resource);
          // 获取到mapper xml文件输入流
          InputStream inputStream = Resources.getResourceAsStream(resource);
          // 创建解析器
          XMLMapperBuilder mapperParser = 
              new XMLMapperBuilder(inputStream, configuration, resource,
                                   configuration.getSqlFragments());
          // mapper解析
          mapperParser.parse();
        } else if (resource == null && url != null && mapperClass == null) {
          ErrorContext.instance().resource(url);
          InputStream inputStream = Resources.getUrlAsStream(url);
          XMLMapperBuilder mapperParser = 
              new XMLMapperBuilder(inputStream, configuration, url,
                                   configuration.getSqlFragments());
          mapperParser.parse();
        } else if (resource == null && url == null && mapperClass != null) {
          Class<?> mapperInterface = Resources.classForName(mapperClass);
          configuration.addMapper(mapperInterface);
        } else {
          throw new BuilderException("...");
        }
      }
    }
  }
}

我们先分析示例中的流程,其余的流程有余力再补充。

mapperParser.parse()方法

public void parse() {
  if (!configuration.isResourceLoaded(resource)) {
    // 这个是核心方法
    configurationElement(parser.evalNode("/mapper"));
    configuration.addLoadedResource(resource);
    bindMapperForNamespace();
  }

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

这行代码是核心:

configurationElement(parser.evalNode("/mapper"));

做了以下事情:

  • 解析namespace
  • 解析cache-ref和cache
  • 解析parameterMap
  • 解析resultMap
  • 解析sql
  • 解析select、insert、update、delete等statement
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("...");
  }
}

我们重点分析解析statement、parameter map和result map这几个部分。

解析statement

MappedStatement类

在mybatis中,使用MappedStatement类封装SQL语句:

public final class MappedStatement {

  private String resource;
  // 全局Configuration
  private Configuration configuration;
  // 使用namespace前缀的id
  private String id;
  private Integer fetchSize;
  private Integer timeout;
  // STATEMENT, PREPARED(默认), CALLABLE
  // 通常不设置
  private StatementType statementType;
  // 通常不设置
  private ResultSetType resultSetType;
  // 用于获取BoundSql
  private SqlSource sqlSource;
  // 缓存,不是本文分析的重点
  private Cache cache;
  // parameterMap id
  private ParameterMap parameterMap;
  // ResultMap集合
  private List<ResultMap> resultMaps;

  private boolean flushCacheRequired;
  private boolean useCache;
  private boolean resultOrdered;

  // UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH
  private SqlCommandType sqlCommandType;
  // 不是本文分析的重点
  private KeyGenerator keyGenerator;
  private String[] keyProperties;
  private String[] keyColumns;

  // 是否包含了嵌套ResultMap
  private boolean hasNestedResultMaps;

  private String databaseId;
  private Log statementLog;
  private LanguageDriver lang;
  private String[] resultSets;
}

在Configuration中使用Map结构保存MappedStatement如下:

protected final Map<String, MappedStatement> mappedStatements = 
    new StrictMap<MappedStatement>("Mapped Statements collection");

StrictMap是mybatis编写的一个Map实现,继承了HashMap类,重写了put和get方法,方法前后加入了一些mybatis的逻辑。

核心代码

buildStatementFromContext(context.evalNodes("select|insert|update|delete"));

buildStatementFromContext方法:

// 方法接收一个node集合,节点可以是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) {
      // 解析出错,加入到一个集合
      // 后续在parsePendingStatements()这里处理解析异常的statement语句
      configuration.addIncompleteStatement(statementParser);
    }
  }
}

下面就重点分析一下statementParser.parseStatementNode()方法:

public void parseStatementNode() {
  String id = context.getStringAttribute("id");
  String databaseId = context.getStringAttribute("databaseId");

  if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
    return;
  }

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

  // Include Fragments before parsing
  XMLIncludeTransformer includeParser = 
      new XMLIncludeTransformer(configuration, builderAssistant);
  includeParser.applyIncludes(context.getNode());

  // 解析parameterType
  String parameterType = context.getStringAttribute("parameterType");
  Class<?> parameterTypeClass = resolveClass(parameterType);

  String lang = context.getStringAttribute("lang");
  LanguageDriver langDriver = getLanguageDriver(lang);

  // Parse selectKey after includes and remove them.
  // 解析<selectKey ...>节点
  processSelectKeyNodes(id, parameterTypeClass, langDriver);

  // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
  KeyGenerator keyGenerator;
  String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
  keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
  if (configuration.hasKeyGenerator(keyStatementId)) {
    keyGenerator = configuration.getKeyGenerator(keyStatementId);
  } else {
    // 解析useGeneratedKeys属性
    keyGenerator = context.getBooleanAttribute(
        "useGeneratedKeys",
        configuration.isUseGeneratedKeys() && 
            SqlCommandType.INSERT.equals(sqlCommandType))
        ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
  }

  // 这个非常重要:将xml中的sql转成SqlSource对象,以便后续的进一步解析、
  // 创建preparedstatement及参数赋值
  SqlSource sqlSource = 
      langDriver.createSqlSource(configuration, context, parameterTypeClass);
  // 这个基本就是默认的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");
  // 解析resultType
  // 此处有个别名的问题,所以使用的是resolveClass(resultType)方法
  String resultType = context.getStringAttribute("resultType");
  Class<?> resultTypeClass = resolveClass(resultType);
  // 解析resultMap
  String resultMap = context.getStringAttribute("resultMap");
  String resultSetType = context.getStringAttribute("resultSetType");
  ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
  if (resultSetTypeEnum == null) {
    resultSetTypeEnum = configuration.getDefaultResultSetType();
  }
  // key对应的属性名
  String keyProperty = context.getStringAttribute("keyProperty");
  // key对应的列名
  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);
}

public MappedStatement addMappedStatement(
    String id,
    SqlSource sqlSource,
    StatementType statementType,
    SqlCommandType sqlCommandType,
    Integer fetchSize,
    Integer timeout,
    String parameterMap,
    Class<?> parameterType,
    String resultMap,
    Class<?> resultType,
    ResultSetType resultSetType,
    boolean flushCache,
    boolean useCache,
    boolean resultOrdered,
    KeyGenerator keyGenerator,
    String keyProperty,
    String keyColumn,
    String databaseId,
    LanguageDriver lang,
    String resultSets) {

  // 将短id转为namespace+短id的格式
  id = applyCurrentNamespace(id, false);
  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) {
    statementBuilder.parameterMap(statementParameterMap);
  }

  MappedStatement statement = statementBuilder.build();

  // 加入到Configuration中
  configuration.addMappedStatement(statement);
  return statement;
}

public void addMappedStatement(MappedStatement ms) {
  mappedStatements.put(ms.getId(), ms);
}

后续将只分析SqlSource的解析流程,其余的代码不做展开分析。

解析SqlSource入口

SqlSource sqlSource = langDriver
    .createSqlSource(configuration, context, parameterTypeClass);

此处的langDriver对象是org.apache.ibatis.scripting.xmltags.XMLLanguageDriver类型:

public SqlSource createSqlSource(
    Configuration configuration, XNode script, Class<?> parameterType) {
  XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
  return builder.parseScriptNode();
}

解析SqlSource

在XMLScriptBuilder.parseScriptNode方法中:

public SqlSource parseScriptNode() {
  // 这里是动态SQL的解析和判断
  // 比较重点的代码
  MixedSqlNode rootSqlNode = parseDynamicTags(context);
  SqlSource sqlSource;
  if (isDynamic) {
    // 创建动态SqlSource
    sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
  } else {
    sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
  }
  return sqlSource;
}

SqlNode接口

解析mapper中的语句节点:

public interface SqlNode {
  boolean apply(DynamicContext context);
}

实现结构:

SqlNode
  |-- ChooseSqlNode
  |-- ForEachSqlNode
  |-- IfSqlNode
  |-- MixedSqlNode
  |-- StaticTextSqlNode
  |-- TextSqlNode
  |-- TrimSqlNode
    |-- WhereSqlNode
    |-- SetSqlNode
  |-- VarDeclSqlNode

parseDynamicTags(context)方法返回一个MixedSqlNode对象,这个实现比较特殊:

public class MixedSqlNode implements SqlNode {
  private final List<SqlNode> contents;

  public MixedSqlNode(List<SqlNode> contents) {
    this.contents = contents;
  }

  @Override
  public boolean apply(DynamicContext context) {
    contents.forEach(node -> node.apply(context));
    return true;
  }
}

内部是一个SqlNode集合,调用apply方法时会循环调用集合中SqlNode的apply方法,这样就可以实现标签的嵌套解析。

SqlSource接口

Represents the content of a mapped statement read from an XML file or an annotation. It creates the SQL that will be passed to the database out of the input parameter received from the user.

接口定义:

public interface SqlSource {
  BoundSql getBoundSql(Object parameterObject);
}

parameterObject就是调用者传递的参数。

实现类:

SqlSource
  |-- DynamicSqlSource
  |-- RawSqlSource
  |-- StaticSqlSource

BoundSql类封装sql(带?或参数名可以用于创建PreparedStatement)、参数映射和参数对象:

public class BoundSql {

  // 这个sql就是用来创建PreparedStatement的语句,携带?或参数名
  private final String sql;
  // 参数映射
  private final List<ParameterMapping> parameterMappings;
  // 以下几个字段都是与参数相关,后续执行阶段会再做介绍
  private final Object parameterObject;
  private final Map<String, Object> additionalParameters;
  private final MetaObject metaParameters;

  public BoundSql(
      Configuration configuration,
      String sql,
      List<ParameterMapping> parameterMappings,
      Object parameterObject) {
    this.sql = sql;
    this.parameterMappings = parameterMappings;
    this.parameterObject = parameterObject;
    this.additionalParameters = new HashMap<>();
    this.metaParameters = configuration.newMetaObject(additionalParameters);
  }
}

parseDynamicTags方法

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));
    // CDATA或者文本节点
    if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || 
        child.getNode().getNodeType() == Node.TEXT_NODE) {
      String data = child.getStringBody("");
      // 创建一个TextSqlNode
      TextSqlNode textSqlNode = new TextSqlNode(data);
      if (textSqlNode.isDynamic()) {
        contents.add(textSqlNode);
        isDynamic = true;
      } else {
        // 非动态SQL则再封装一层StaticTextSqlNode
        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");
      }
      // 解析标签
      handler.handleNode(child, contents);
      // 标记动态SQL
      isDynamic = true;
    }
  }
  // 返回MixedSqlNode对象
  return new MixedSqlNode(contents);
}

NodeHandler接口:

private interface NodeHandler {
  void handleNode(XNode nodeToHandle, List<SqlNode> targetContents);
}

NodeHandler实现结构,和SqlNode接口类似:

NodeHandler
  |-- BindHandler
  |-- TrimHandler
  |-- WhereHandler
  |-- SetHandler
  |-- ForEachHandler
  |-- IfHandler
  |-- OtherwiseHandler
  |-- ChooseHandler

在这里注册的:

private void initNodeHandlerMap() {
  nodeHandlerMap.put("trim", new TrimHandler());
  nodeHandlerMap.put("where", new WhereHandler()); // 常用
  nodeHandlerMap.put("set", new SetHandler()); // 常用
  nodeHandlerMap.put("foreach", new ForEachHandler()); // 常用
  nodeHandlerMap.put("if", new IfHandler()); // 常用
  nodeHandlerMap.put("choose", new ChooseHandler());
  nodeHandlerMap.put("when", new IfHandler());
  nodeHandlerMap.put("otherwise", new OtherwiseHandler());
  nodeHandlerMap.put("bind", new BindHandler());
}

StaticSqlSource类

在介绍DynamicSqlSource和RawSqlSource之前,先看一下这个类,因为其余的两个SqlSource实现内部都依赖了StaticSqlSource类。

public class StaticSqlSource implements SqlSource {

  private final String sql;
  private final List<ParameterMapping> parameterMappings;
  private final Configuration configuration;

  public StaticSqlSource(Configuration configuration, String sql) {
    this(configuration, sql, null);
  }

  public StaticSqlSource(
      Configuration configuration,
      String sql,
      List<ParameterMapping> parameterMappings) {
    this.sql = sql;
    this.parameterMappings = parameterMappings;
    this.configuration = configuration;
  }

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

实现方式很简单,从getBoundSql方法的实现来看,sql和parameterMappings的解析工作应该是在上层的DynamicSqlSource和RawSqlSource实现里面做的。

创建RawSqlSource

sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);

RawSqlSource实现:

public class RawSqlSource implements SqlSource {

  private final SqlSource sqlSource;

  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;
    // 这里会生成一个StaticSqlSource对象
    sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap<>());
  }

  private static String getSql(Configuration configuration, SqlNode rootSqlNode) {
    // 解析MixedSqlNode
    DynamicContext context = new DynamicContext(configuration, null);
    rootSqlNode.apply(context);
    return context.getSql();
  }

  @Override
  public BoundSql getBoundSql(Object parameterObject) {
    return sqlSource.getBoundSql(parameterObject);
  }
}

我们使用一个例子简单说明一下。

配置的SQL如下:

<select id="selectBlogByParameterTest" resultMap="BlogResultMap"
  parameterType="org.net5ijy.mybatis.test.parameter.BlogSearchParameter">
  select * from blog where
  id = #{id} and
  title like concat(concat('%',#{blog.title}),'%')
</select>

在创建RawSqlSource的时候:

  • rootSqlNode是一个MixedSqlNode对象,里面只有一个StaticTextSqlNode对象,封装着配置文件中的sql语句

    select * from blog where
    id = #{id} and
    title like concat(concat('%',#{blog.title}),'%')
    
  • parameterType是BlogSearchParameter,这个是从配置文件解析出来的

  • configuration还是全局的Configuration对象,封装着所有的配置信息

getSql(configuration, rootSqlNode)方法会把SqlNode解析出一个SQL字符串,本示例中就比较简单,就是直接把配置的SQL返回。

sqlSourceParser.parse(sql, clazz, new HashMap<>())方法比较重要,他通过SQL字符串和参数类型parameterType解析出ParameterMapping集合、PreparedStatement SQL并使用这些对象创建一个StaticSqlSource对象:

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

主要做两件事:

  • 解析SQL中的#{}参数,实现方式有很多种,比如正则表达式,或者像mybatis这样自己编写解析逻辑
  • 使用参数创建parameterMapping并使用?替换#{}参数

代码就不展开分析了。

经过上述处理后,RawSqlSource内部就封装了一个StaticSqlSource对象,StaticSqlSource对象内部又封装着PreparedStatement SQL和ParameterMapping集合。

在调用getBoundSql方法时,就是调用StaticSqlSource的getBoundSql方法:

// 前三个参数都是StaticSqlSource的成员变量
return new BoundSql(configuration, sql, parameterMappings, parameterObject);

创建DynamicSqlSource

sqlSource = new DynamicSqlSource(configuration, rootSqlNode);

传递Configuration和之前创建的MixedSqlNode对象作为参数:

public class DynamicSqlSource implements SqlSource {

  private final Configuration configuration;
  private final SqlNode rootSqlNode;

  public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) {
    this.configuration = configuration;
    this.rootSqlNode = rootSqlNode;
  }

  @Override
  public BoundSql getBoundSql(Object parameterObject) {
    // 此处略,后续有详细分析
  }
}

使用一个例子简单说明一下。

配置的SQL如下:

<select id="selectBlogByParameter" resultMap="BlogResultMap"
  parameterType="org.net5ijy.mybatis.test.parameter.BlogSearchParameter">
  select * from blog
  <where>
    <if test="id != null">
      and id = #{id}
    </if>
    <if test="ids != null and ids.size > 0">
      and id in
      <foreach collection="ids" item="id" separator="," open="(" close=")">
        #{id}
      </foreach>
    </if>
    <if test="blog.title != null">
      and title like concat(concat('%',#{blog.title}),'%')
    </if>
    <if test="blog.content != null">
      and content like concat(concat('%',#{blog.content}),'%')
    </if>
    <if test="createTime != null">
      and create_time <![CDATA[ > ]]> #{createTime}
    </if>
  </where>
</select>

在创建DynamicSqlSource的时候:

  • rootSqlNode是一个MixedSqlNode对象,结构如下:

    • 0 - 为select * from blog
    • 1 - 为WhereSqlNode对象,内部又封装着一个MixedSqlNode对象,嵌套着where标签下面的动态SQL
    • 2 - 末尾的换行符
      在这里插入图片描述
  • parameterType是BlogSearchParameter,这个是从配置文件解析出来的

  • configuration还是全局的Configuration对象,封装着所有的配置信息

创建DynamicSqlSource对象只是简单地赋值,重点在getBoundSql方法:

public BoundSql getBoundSql(Object parameterObject) {
  // 解析动态SqlNode
  DynamicContext context = new DynamicContext(configuration, parameterObject);
  rootSqlNode.apply(context);
  SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
  Class<?> parameterType = 
      parameterObject == null ? Object.class : parameterObject.getClass();
  // 这里会生成一个StaticSqlSource对象
  SqlSource sqlSource = 
      sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
  // 获取到BoundSql
  BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
  context.getBindings().forEach(boundSql::setAdditionalParameter);
  return boundSql;
}

动态SQL不是固定的,而是取决于参数,所以在加载阶段无法确定,只能在运行阶段根据传入的参数动态生成,所以:

DynamicContext context = new DynamicContext(configuration, parameterObject);
rootSqlNode.apply(context);

这两行代码就是根据传入的参数动态生成带有#{}参数的SQL语句,之后做的事情就和RawSqlSource在构造方法里面做的一样了,此处不再重复说明。

解析parameter map

这部分比较简单。

入口在XMLMapperBuilder.configurationElement方法:

private void configurationElement(XNode context) {
  try {
    // 上面还有很多代码
    // 这里是解析所有的parameterMap节点
    parameterMapElement(context.evalNodes("/mapper/parameterMap"));
    // 下面还有很多代码
  } catch (Exception e) {
    throw new BuilderException("Error parsing Mapper XML.");
  }
}
private void parameterMapElement(List<XNode> list) {
  // 遍历所有的parameterMap节点
  for (XNode parameterMapNode : list) {
    // 解析id属性
    String id = parameterMapNode.getStringAttribute("id");
    // 解析type属性
    String type = parameterMapNode.getStringAttribute("type");
    // 解析parameterMap所属的Java类型,是一个复杂类型
    Class<?> parameterClass = resolveClass(type);
    // 解析所有的parameter子节点
    List<XNode> parameterNodes = parameterMapNode.evalNodes("parameter");
    List<ParameterMapping> parameterMappings = new ArrayList<>();
    for (XNode parameterNode : parameterNodes) {
      String property = parameterNode.getStringAttribute("property");
      String javaType = parameterNode.getStringAttribute("javaType");
      String jdbcType = parameterNode.getStringAttribute("jdbcType");
      String resultMap = parameterNode.getStringAttribute("resultMap");
      String mode = parameterNode.getStringAttribute("mode");
      String typeHandler = parameterNode.getStringAttribute("typeHandler");
      Integer numericScale = parameterNode.getIntAttribute("numericScale");
      ParameterMode modeEnum = resolveParameterMode(mode);
      Class<?> javaTypeClass = resolveClass(javaType);
      JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
      Class<? extends TypeHandler<?>> typeHandlerClass = resolveClass(typeHandler);
      // 根据parameter子节点配置创建一个ParameterMapping对象,封装属性-Java类型-数据库类型关系
      ParameterMapping parameterMapping = 
          builderAssistant.buildParameterMapping(
              parameterClass, property, javaTypeClass,
              jdbcTypeEnum, resultMap, modeEnum, typeHandlerClass, numericScale);
      parameterMappings.add(parameterMapping);
    }
    // 加入到Configuration中
    builderAssistant.addParameterMap(id, parameterClass, parameterMappings);
  }
}

// builderAssistant.addParameterMap
public ParameterMap addParameterMap(
    String id, Class<?> parameterClass, List<ParameterMapping> parameterMappings) {
  // 拼接namespace
  id = applyCurrentNamespace(id, false);
  ParameterMap parameterMap = 
      new ParameterMap.Builder(configuration, id, parameterClass, parameterMappings)
          .build();
  configuration.addParameterMap(parameterMap);
  return parameterMap;
}

// configuration.addParameterMap
public void addParameterMap(ParameterMap pm) {
  parameterMaps.put(pm.getId(), pm);
}

ParameterMapping类,封装参数映射信息,核心功能就是维护实体属性名、Java类型、数据库类型的关系(比如属性名name,Java类型是String,数据库类型是Varchar):

public class ParameterMapping {

  private Configuration configuration;

  private String property;
  private ParameterMode mode;
  private Class<?> javaType = Object.class;
  private JdbcType jdbcType;
  private Integer numericScale;
  private TypeHandler<?> typeHandler;
  private String resultMapId;
  private String jdbcTypeName;
  private String expression;
}

ParameterMap类,又做了一层封装:

public class ParameterMap {

  private String id;
  private Class<?> type;
  private List<ParameterMapping> parameterMappings;
}

解析result map

使用的是resultMap子标签实现。

resultMap的解析过程比较复杂,后续分析执行流程时会再补充此部分内容。

resultMap标签

resultMap支持的属性:

  • id - REQUIRED
  • type - REQUIRED
  • extends - IMPLIED
  • autoMapping - (true|false)

resultMap支持的子标签:

  • constructor?
  • id*
  • result*
  • association*
  • collection*
  • discriminator?

以下为dtd内容:

<!ELEMENT resultMap (constructor?,id*,result*,association*,collection*, discriminator?)>
<!ATTLIST resultMap
id CDATA #REQUIRED
type CDATA #REQUIRED
extends CDATA #IMPLIED
autoMapping (true|false) #IMPLIED
>

<!ELEMENT constructor (idArg*,arg*)>

<!ELEMENT id EMPTY>
<!ATTLIST id
property CDATA #IMPLIED
javaType CDATA #IMPLIED
column CDATA #IMPLIED
jdbcType CDATA #IMPLIED
typeHandler CDATA #IMPLIED
>

<!ELEMENT result EMPTY>
<!ATTLIST result
property CDATA #IMPLIED
javaType CDATA #IMPLIED
column CDATA #IMPLIED
jdbcType CDATA #IMPLIED
typeHandler CDATA #IMPLIED
>

<!ELEMENT idArg EMPTY>
<!ATTLIST idArg
javaType CDATA #IMPLIED
column CDATA #IMPLIED
jdbcType CDATA #IMPLIED
typeHandler CDATA #IMPLIED
select CDATA #IMPLIED
resultMap CDATA #IMPLIED
name CDATA #IMPLIED
columnPrefix CDATA #IMPLIED
>

<!ELEMENT arg EMPTY>
<!ATTLIST arg
javaType CDATA #IMPLIED
column CDATA #IMPLIED
jdbcType CDATA #IMPLIED
typeHandler CDATA #IMPLIED
select CDATA #IMPLIED
resultMap CDATA #IMPLIED
name CDATA #IMPLIED
columnPrefix CDATA #IMPLIED
>

<!ELEMENT collection (constructor?,id*,result*,association*,collection*, discriminator?)>
<!ATTLIST collection
property CDATA #REQUIRED
column CDATA #IMPLIED
javaType CDATA #IMPLIED
ofType CDATA #IMPLIED
jdbcType CDATA #IMPLIED
select CDATA #IMPLIED
resultMap CDATA #IMPLIED
typeHandler CDATA #IMPLIED
notNullColumn CDATA #IMPLIED
columnPrefix CDATA #IMPLIED
resultSet CDATA #IMPLIED
foreignColumn CDATA #IMPLIED
autoMapping (true|false) #IMPLIED
fetchType (lazy|eager) #IMPLIED
>

<!ELEMENT association (constructor?,id*,result*,association*,collection*, discriminator?)>
<!ATTLIST association
property CDATA #REQUIRED
column CDATA #IMPLIED
javaType CDATA #IMPLIED
jdbcType CDATA #IMPLIED
select CDATA #IMPLIED
resultMap CDATA #IMPLIED
typeHandler CDATA #IMPLIED
notNullColumn CDATA #IMPLIED
columnPrefix CDATA #IMPLIED
resultSet CDATA #IMPLIED
foreignColumn CDATA #IMPLIED
autoMapping (true|false) #IMPLIED
fetchType (lazy|eager) #IMPLIED
>

<!ELEMENT discriminator (case+)>
<!ATTLIST discriminator
column CDATA #IMPLIED
javaType CDATA #REQUIRED
jdbcType CDATA #IMPLIED
typeHandler CDATA #IMPLIED
>

<!ELEMENT case (constructor?,id*,result*,association*,collection*, discriminator?)>
<!ATTLIST case
value CDATA #REQUIRED
resultMap CDATA #IMPLIED
resultType CDATA #IMPLIED
>

可以看到constructor、collection、association、discriminator这几个子标签比较特别,因为这些标签支持嵌套,解析的时候必定会有递归或循环。

ResultMap类

public class ResultMap {
  private Configuration configuration;

  private String id;
  private Class<?> type;
  private List<ResultMapping> resultMappings;
  private List<ResultMapping> idResultMappings;
  private List<ResultMapping> constructorResultMappings;
  private List<ResultMapping> propertyResultMappings;
  private Set<String> mappedColumns;
  private Set<String> mappedProperties;
  private Discriminator discriminator;
  private boolean hasNestedResultMaps;
  private boolean hasNestedQueries;
  private Boolean autoMapping;
}

解析result map

源码入口:

private void configurationElement(XNode context) {
  try {
    // 上面还有很多代码
    // 这里是解析所有的resultMap节点
    resultMapElements(context.evalNodes("/mapper/resultMap"));
    // 下面还有很多代码
  } catch (Exception e) {
    throw new BuilderException("Error parsing Mapper XML.");
  }
}

private void resultMapElements(List<XNode> list) {
  // 遍历所有的resultMap节点
  for (XNode resultMapNode : list) {
    try {
      resultMapElement(resultMapNode);
    } catch (IncompleteElementException e) {
      // ignore, it will be retried
    }
  }
}

后续会进入到resultMapElement方法,这个方法包含解析一个resultMap标签的核心逻辑:

// 这里的resultMapNode就是resultMap标签
private ResultMap resultMapElement(
    XNode resultMapNode,
    List<ResultMapping> additionalResultMappings,
    Class<?> enclosingType) {

  // 解析result类型
  // 因为association、collection、case也会使用这个方法来解析,所以此处做了兼容
  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);
  }

  Discriminator discriminator = null;

  // 封装ResultMapping集合
  List<ResultMapping> resultMappings = new ArrayList<>(additionalResultMappings);

  // 获取到所有的子标签
  List<XNode> resultChildren = resultMapNode.getChildren();
  for (XNode resultChild : resultChildren) {
    if ("constructor".equals(resultChild.getName())) {
      // 处理constructor标签
      processConstructorElement(resultChild, typeClass, resultMappings);
    } else if ("discriminator".equals(resultChild.getName())) {
      // 处理discriminator标签
      discriminator = 
          processDiscriminatorElement(resultChild, typeClass, resultMappings);
    } else {
      List<ResultFlag> flags = new ArrayList<>();
      if ("id".equals(resultChild.getName())) {
        flags.add(ResultFlag.ID);
      }
      resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
    }
  }
  // 并不是每一个ResultMap都有id,比如嵌套的ResultMap就不存在,
  // 所有使用resultMapNode.getValueBasedIdentifier()方法做了兼容
  String id = resultMapNode.getStringAttribute("id",
          resultMapNode.getValueBasedIdentifier());
  String extend = resultMapNode.getStringAttribute("extends");
  Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");

  // 创建一个ResultMapResolver
  ResultMapResolver resultMapResolver = new ResultMapResolver(
      builderAssistant, id, typeClass, extend,
      discriminator, resultMappings, autoMapping);
  try {
    // 最终还是add到Configuration里面
    return resultMapResolver.resolve();
  } catch (IncompleteElementException e) {
    configuration.addIncompleteResultMap(resultMapResolver);
    throw e;
  }
}

// 创建ResultMap
public ResultMap addResultMap(
    String id,
    Class<?> type,
    String extend,
    Discriminator discriminator,
    List<ResultMapping> resultMappings,
    Boolean autoMapping) {

  id = applyCurrentNamespace(id, false);
  extend = applyCurrentNamespace(extend, true);

  if (extend != null) {
    if (!configuration.hasResultMap(extend)) {
      throw new IncompleteElementException("");
    }
    ResultMap resultMap = configuration.getResultMap(extend);
    List<ResultMapping> extendedResultMappings = 
        new ArrayList<>(resultMap.getResultMappings());
    extendedResultMappings.removeAll(resultMappings);
    // Remove parent constructor if this resultMap declares a constructor.
    boolean declaresConstructor = false;
    for (ResultMapping resultMapping : resultMappings) {
      if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) {
        declaresConstructor = true;
        break;
      }
    }
    if (declaresConstructor) {
      extendedResultMappings.removeIf(
          resultMapping -> resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR));
    }
    resultMappings.addAll(extendedResultMappings);
  }
  ResultMap resultMap = 
      new ResultMap.Builder(configuration, id, type, resultMappings, autoMapping)
                   .discriminator(discriminator).build();
  configuration.addResultMap(resultMap);
  return resultMap;
}

ResultMapping类

public class ResultMapping {

  private Configuration configuration;
  private String property;
  private String column;
  private Class<?> javaType;
  private JdbcType jdbcType;
  private TypeHandler<?> typeHandler;
  private String nestedResultMapId;
  private String nestedQueryId;
  private Set<String> notNullColumns;
  private String columnPrefix;
  private List<ResultFlag> flags;
  private List<ResultMapping> composites;
  private String resultSet;
  private String foreignColumn;
  private boolean lazy;
}

buildResultMappingFromContext方法

先看一下buildResultMappingFromContext方法,这个是子标签解析中默认分支的核心处理逻辑,所以他主要处理id、result、association、collection标签:

private ResultMapping buildResultMappingFromContext(
    XNode context, Class<?> resultType, List<ResultFlag> flags) {
  String property;
  // 因为constructor标签的解析过程也会使用到这个方法,所以此处做了兼容
  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");

  // association、collection子标签支持一个嵌套查询
  String nestedSelect = context.getStringAttribute("select");

  // 如果获取到了resultMap属性,就直接使用
  // 如果没有获取到,就尝试去解析子标签来获取嵌套的ResultMap
  String nestedResultMap = context.getStringAttribute("resultMap", () ->
      processNestedResultMappings(context, Collections.emptyList(), resultType));

  String notNullColumn = context.getStringAttribute("notNullColumn");
  String columnPrefix = context.getStringAttribute("columnPrefix");
  String typeHandler = context.getStringAttribute("typeHandler");
  String resultSet = context.getStringAttribute("resultSet");
  String foreignColumn = context.getStringAttribute("foreignColumn");
  boolean lazy = "lazy".equals(
      context.getStringAttribute(
          "fetchType", configuration.isLazyLoadingEnabled() ? "lazy" : "eager"));

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

// 解析嵌套的ResultMap
private String processNestedResultMappings(
    XNode context, List<ResultMapping> resultMappings, Class<?> enclosingType) {
  if (Arrays.asList("association", "collection", "case").contains(context.getName())
      && context.getStringAttribute("select") == null) {
    // 验证,不做展开分析
    validateCollection(context, enclosingType);
    // 再使用resultMapElement进行解析
    ResultMap resultMap = resultMapElement(context, resultMappings, enclosingType);
    // 把id返回
    return resultMap.getId();
  }
  return null;
}

// 创建ResultMapping
public ResultMapping buildResultMapping(
    Class<?> resultType,
    String property,
    String column,
    Class<?> javaType,
    JdbcType jdbcType,
    String nestedSelect,
    String nestedResultMap,
    String notNullColumn,
    String columnPrefix,
    Class<? extends TypeHandler<?>> typeHandler,
    List<ResultFlag> flags,
    String resultSet,
    String foreignColumn,
    boolean lazy) {

  Class<?> javaTypeClass = resolveResultJavaType(resultType, property, javaType);
  TypeHandler<?> typeHandlerInstance = resolveTypeHandler(javaTypeClass, typeHandler);
  List<ResultMapping> composites;
  if ((nestedSelect == null || nestedSelect.isEmpty()) && 
      (foreignColumn == null || foreignColumn.isEmpty())) {
    composites = Collections.emptyList();
  } else {
    composites = parseCompositeColumnName(column);
  }
  return new ResultMapping.Builder(configuration, property, column, javaTypeClass)
      .jdbcType(jdbcType)
      .nestedQueryId(applyCurrentNamespace(nestedSelect, true))
      .nestedResultMapId(applyCurrentNamespace(nestedResultMap, true))
      .resultSet(resultSet)
      .typeHandler(typeHandlerInstance)
      .flags(flags == null ? new ArrayList<>() : flags)
      .composites(composites)
      .notNullColumns(parseMultipleColumnNames(notNullColumn))
      .columnPrefix(columnPrefix)
      .foreignColumn(foreignColumn)
      .lazy(lazy)
      .build();
}

解析constructor标签

private void processConstructorElement(
    XNode resultChild, Class<?> resultType, List<ResultMapping> resultMappings) {
  List<XNode> argChildren = resultChild.getChildren();
  for (XNode argChild : argChildren) {
    List<ResultFlag> flags = new ArrayList<>();
    flags.add(ResultFlag.CONSTRUCTOR);
    if ("idArg".equals(argChild.getName())) {
      flags.add(ResultFlag.ID);
    }
    resultMappings.add(buildResultMappingFromContext(argChild, resultType, flags));
  }
}

解析discriminator标签

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

Open Session代码分析

SqlSession session = sessionFactory.openSession();

这里会进入DefaultSqlSessionFactory的openSession方法:

public SqlSession openSession() {
  return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}

需要三个参数:

  • ExecutorType - 可选SIMPLE, REUSE, BATCH三种,默认SIMPLE,这个是可以配置的,后面介绍
  • TransactionIsolationLeve - 事务隔离级别,不介绍了
  • boolean autoCommit - 自动提交,默认false

重载的openSession方法

public interface SqlSessionFactory {

  SqlSession openSession();

  SqlSession openSession(boolean autoCommit);

  SqlSession openSession(Connection connection);

  SqlSession openSession(TransactionIsolationLevel level);

  SqlSession openSession(ExecutorType execType);

  SqlSession openSession(ExecutorType execType, boolean autoCommit);

  SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level);

  SqlSession openSession(ExecutorType execType, Connection connection);
}

参数名见名知意,不做过多介绍了。

defaultExecutorType参数

configuration.getDefaultExecutorType()从Configuration中获取,在XMLConfigBuilder.settingsElement方法中进行初始化:

private void settingsElement(Properties props) {
  // 其他配置
  configuration.setDefaultExecutorType(
      ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));
  // 其他配置
}

而这个方法在parseConfiguration(XNode root)被调用:

// 只记录需要的部分代码,其余代码省略
private void parseConfiguration(XNode root) {
  try {
    Properties settings = settingsAsProperties(root.evalNode("settings"));
    loadCustomVfs(settings);
    loadCustomLogImpl(settings);
    settingsElement(settings);
  } catch (Exception e) {
    throw new BuilderException("Error parsing SQL Mapper Configuration. Cause ...");
  }
}

所以可以通过配置修改defaultExecutorType的值:

<settings>
  <setting name="defaultExecutorType" value="SIMPLE"/>
</settings>

从settingsElement(Properties)方法我们可以得到settings支持的所有配置项。

openSessionFromDataSource方法

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);
    // 创建Executor
    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();
  }
}

创建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 {
    // 这里创建一个SimpleExecutor
    executor = new SimpleExecutor(this, transaction);
  }
  if (cacheEnabled) {
    // cacheEnabled默认是true
    // 创建CachingExecutor
    executor = new CachingExecutor(executor);
  }
  // 这里是插件
  executor = (Executor) interceptorChain.pluginAll(executor);
  // 返回
  return executor;
}

插件 - pluginAll

executor = (Executor) interceptorChain.pluginAll(executor);

pluginAll方法:

public Object pluginAll(Object target) {
  for (Interceptor interceptor : interceptors) {
    target = interceptor.plugin(target);
  }
  return target;
}

default Object plugin(Object target) {
  return Plugin.wrap(target, this);
}

// 代理
// 这个类的代码有助于理解mybatis插件原理,所以都记录在这里了
public class Plugin implements InvocationHandler {

  private final Object target;
  private final Interceptor interceptor;
  private final Map<Class<?>, Set<Method>> signatureMap;

  private Plugin(
      Object target, Interceptor interceptor,
      Map<Class<?>, Set<Method>> signatureMap) {
    this.target = target;
    this.interceptor = interceptor;
    this.signatureMap = signatureMap;
  }

  public static Object wrap(Object target, Interceptor interceptor) {
    // 用于判断是否需要拦截目标方法
    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;
  }

  @Override
  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);
    }
  }

  // 解析标注在Interceptor实现类上的@Intercepts和@Signature注解
  private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
    Intercepts interceptsAnnotation =
        interceptor.getClass().getAnnotation(Intercepts.class);
    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("...");
      }
    }
    return signatureMap;
  }

  private static Class<?>[] getAllInterfaces(
      Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {
    Set<Class<?>> interfaces = new HashSet<>();
    while (type != null) {
      for (Class<?> c : type.getInterfaces()) {
        if (signatureMap.containsKey(c)) {
          interfaces.add(c);
        }
      }
      type = type.getSuperclass();
    }
    return interfaces.toArray(new Class<?>[0]);
  }
}

下图说明了mybatis插件可以作用的组件:
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值