写在前面

这篇文章中分析了解析mapper xml的<mappers>中所有子标签的过程,其中的statement相关的标签解析过程比较复杂,因此单独在这篇文章中分析。
想要系统学习的,可以参考这篇文章,重要!!!

1:入口

源码如下:

org.apache.ibatis.builder.xml.XMLMapperBuilder#configurationElement
private void configurationElement(XNode context) {
  try {
    ...snip...
    // <202108021631>
    buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
  } catch (Exception e) {
    ...snip...
  }
}

<202108021631>处源码如下:

org.apache.ibatis.builder.xml.XMLMapperBuilder#buildStatementFromContext(java.util.List<org.apache.ibatis.parsing.XNode>)
private void buildStatementFromContext(List<XNode> list) {
  // 如果有全局databseId
  if (configuration.getDatabaseId() != null) {
    buildStatementFromContext(list, configuration.getDatabaseId());
  }
  // <202108021633>
  buildStatementFromContext(list, null);
}

<202108021633>处源码如下:

org.apache.ibatis.builder.xml.XMLMapperBuilder#buildStatementFromContext(java.util.List<org.apache.ibatis.parsing.XNode>, java.lang.String)
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
  // 循环处理每个statement
  for (XNode context : list) {
    // 创建XMLStatementBuilder类
    final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
    try {
      // <202108021754>
      statementParser.parseStatementNode();
    } catch (IncompleteElementException e) {
      // 发生异常,添加到不完备的statement集合中
      // protected final Collection<XMLStatementBuilder> incompleteStatements = new LinkedList<XMLStatementBuilder>();
      configuration.addIncompleteStatement(statementParser);
    }
  }
}

<202108021754>处是使用XMLStatementBuilder类解析statement,详细参考2:解析statement

2:解析statement

源码如下:

org.apache.ibatis.builder.xml.XMLStatementBuilder#parseStatementNode
public void parseStatementNode() {
  // 获取id属性,和namespace组成该statement的唯一标识
  String id = context.getStringAttribute("id");
  // 获取databaseId,一般我们不设置,用于指定数据库厂商,可以默认为mysql
  String databaseId = context.getStringAttribute("databaseId");
  // 判断数据库厂商是否符合要求
  if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) return;
  // 下面是解析各种各样的属性,具体可以先不用看,等用到了再细看
  Integer fetchSize = context.getIntAttribute("fetchSize");
  Integer timeout = context.getIntAttribute("timeout");
  String parameterMap = context.getStringAttribute("parameterMap");
  String parameterType = context.getStringAttribute("parameterType");
  Class<?> parameterTypeClass = resolveClass(parameterType);
  String resultMap = context.getStringAttribute("resultMap");
  String resultType = context.getStringAttribute("resultType");
  String lang = context.getStringAttribute("lang");
  // 根据用户配置的lang属性获取对应的LanguageDriver
  // <202108051511>
  LanguageDriver langDriver = getLanguageDriver(lang);
  // 获取resultType的class类型
  Class<?> resultTypeClass = resolveClass(resultType);
  // 获取resultSet的值,配置的是JDBC的ResultSet的类型,一般不用,暂时忽略
  String resultSetType = context.getStringAttribute("resultSetType");
  // <202108051554>
  // 获取配置的statementType属性值,并通过枚举类valueOf方法,通过字面量(区分大小写)获取对应
  // 枚举对象
  StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
  ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
  // 获取节点名称,可能insert|select|delete|update
  String nodeName = context.getNode().getNodeName();
  // 转换为SqlCommandType枚举类,定义如下:
  /*
  public enum SqlCommandType {
	  UNKNOWN, INSERT, UPDATE, DELETE, SELECT;
  }
  */
  SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
  // 是否为select标记
  boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
  // 配置的是否刷新缓存,默认值是只要不是select则为true(!isSelect)
  boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
  // 是否使用缓存的标记,默认值是只要是select则使用缓存(isSelect)
  boolean useCache = context.getBooleanAttribute("useCache", isSelect);
  boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
  // 创建用于替换<include/>标签的XMLIncludeTransformer对象
  XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
  // <202108051720> 
  // 应用节点内部的<include/>标签
  includeParser.applyIncludes(context.getNode());
  // 解析<selectKey/>标签,该标签用于设置在非数据库自动生成id情况下生成主键值
  // <202108091400>
  processSelectKeyNodes(id, parameterTypeClass, langDriver);
  // 创建SqlSource,该接口用于封装statement内配置的信息
  // <202108161409>
  SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
  // 解析statement中的resultSet属性,其实只在<select/>标签中有该属性
  String resultSets = context.getStringAttribute("resultSets");
  String keyProperty = context.getStringAttribute("keyProperty");
  String keyColumn = context.getStringAttribute("keyColumn");
  KeyGenerator keyGenerator;
  // 组装KeyGenerator的key,在前面解析<selectKey/>过程已经使用该key存储到configuration中
  String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
  // 添加命名空间前缀,如果需要的话
  keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
  // if为true的话,说明配置了<selectKey>标签
  if (configuration.hasKeyGenerator(keyStatementId)) {
    // 直接从configuration中获取已经解析完毕的KeyGenerator
    keyGenerator = configuration.getKeyGenerator(keyStatementId);
  } else {
    // 如果是useGeneratedKeys属性配置为true,没有配置的话取"configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType)"
    // 的布尔结果值,最终为true的则使用内置的Jdbc3KeyGenerator,否则使用NoKeyGenerator
    keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
        configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
        ? new Jdbc3KeyGenerator() : new NoKeyGenerator();
  }
  // 将当前的statement构建为MappedStatement并添加到configuration中
  // <202108091718>
  builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
      fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
      resultSetTypeEnum, flushCache, useCache, resultOrdered, 
      keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}

<202108051511>处是根据lang属性获取对应的LanguageDriver对象,具体参考2.1:解析lang属性<202108051554>处是获取statementType属性,该属性用来设置底层使用的statement的类型,如果值STATEMENT,则对应的就是com.mysql.jdbc.Statement,如果是PREPARED,则对应的就是com.mysql.jdbc.PreparedStatement,如果是CALLABLE,则对应的就是com.mysql.jdbc.CallableStatement(存储过程调用),对应的枚举类如下:

org.apache.ibatis.mapping.StatementType
public enum StatementType {
  STATEMENT, PREPARED, CALLABLE
}

默认值是PREPARED,使用预编译的API PreparedStatement,该API可以有效防止sql注入问题,此时参数的格式使用#{},最终会使用?方式执行sql语句,如果是Statement则使用${},最终会直接获取参数进行拼接,会存在sql注入问题。假设我们设置了statementType="STATEMENT"但是却使用了#{}`则会报错,如下配置:

<select id="fetchById" resultMap="myResultMap" lang="MyLanguageDriver" statementType="STATEMENT">
    SELECT * FROM test_language_driver t WHERE t.`id`=#{id}
</select>

执行的话,会报如下错:
在这里插入图片描述
错误的原因是因为在mysql中直接执行了带有?的sql语句,肯定是会报语法错误的。<202108051720>处源码是解析节点内的<include/>标签转换为对应的<sql/>信息,具体参考2.2:解析include标签<202108091400>处是解析selectKey标签,具体参考2.3:解析selectKey标签<202108091718>处是构建org.apache.ibatis.mapping.MappedStatement,具体参考2.4:构建MappedStatement<202108161409>处是解析sql语句信息,包含动态sql语句,具体参考这篇文章

2.1:解析lang属性

源码如下:

org.apache.ibatis.builder.xml.XMLStatementBuilder#getLanguageDriver
private LanguageDriver getLanguageDriver(String lang) {
 Class<?> langClass = null;
  // 解析lang属性对应的Class类型
  if (lang != null) {
    langClass = resolveClass(lang);
  }
  // <202108051528>
  return builderAssistant.getLanguageDriver(langClass);
}

<202108051528>处源码如下:

org.apache.ibatis.builder.MapperBuilderAssistant#getLanguageDriver
public LanguageDriver getLanguageDriver(Class<?> langClass) {
  // 获取langClass的值,不为空则注册到LanguageDriverRegistry中,否则从其中获取默认的
  if (langClass != null) {
    configuration.getLanguageRegistry().register(langClass);
  } else {
    // 获取默认的,默认的为rg.apache.ibatis.scripting.xmltags.XMLLanguageDriver
    langClass = configuration.getLanguageRegistry().getDefaultDriverClass();
  }
  // 根据langClass从LanguageDriverRegitry中获取对应的实例对象
  // 存储在map中:org.apache.ibatis.scripting.LanguageDriverRegistry#LANGUAGE_DRIVER_MAP
  // private final Map<Class<?>, LanguageDriver> LANGUAGE_DRIVER_MAP = new HashMap<Class<?>, LanguageDriver>();
  return configuration.getLanguageRegistry().getDriver(langClass);
}

2.2:解析include标签

源码如下:

org.apache.ibatis.builder.xml.XMLIncludeTransformer#applyIncludes
public void applyIncludes(Node source) {
  // 如果当前节点是include节点
  if(source.getNodeName().equals("include")) {
    // <202108051746>
    // 获取引入的sql节点信息
    Node toInclude = findSqlFragment(getStringAttribute(source, "refid"));
    // 因为引入的sql节点内部可能也使用了inlucde,所以这里递归调用
    applyIncludes(toInclude);
    // 这个if先忽略,暂时不知道用处
    if (toInclude.getOwnerDocument() != source.getOwnerDocument()) {
      toInclude = source.getOwnerDocument().importNode(toInclude, true);
    }
    // 替换include节点为sql节点,toInlcude是sql节点,source是inlucde节点
    source.getParentNode().replaceChild(toInclude, source);
    while (toInclude.hasChildNodes()) {
      toInclude.getParentNode().insertBefore(toInclude.getFirstChild(), toInclude);
    }
    // 移除include自身,因为已经转化为对应的sql了
    toInclude.getParentNode().removeChild(toInclude);
   }  // 如果当前节点类型是Node.ELEMENT_NODE 
	else if (source.getNodeType() == Node.ELEMENT_NODE) {
	// 获取子节点,并递归调用
    NodeList children = source.getChildNodes();
    for (int i=0; i<children.getLength(); i++) {
      applyIncludes(children.item(i));
    }
  }
}

<202108051746>处是获取引用的sql节点信息,源码如下:

org.apache.ibatis.builder.xml.XMLIncludeTransformer#findSqlFragment
private Node findSqlFragment(String refid) {
  // 可能是变量,所以这里进行替换,比如在全局config配置文件中通过`<properties>-><prooerty>`
  // 定义,然后通过${xxx}进行引用
  refid = PropertyParser.parse(refid, configuration.getVariables());
  // 添加命名空间前缀,namespace+refid,以便在多个命名空间中保证唯一性
  refid = builderAssistant.applyCurrentNamespace(refid, true);
  try {
    // 获取引用的sql节点信息
    XNode nodeToInclude = configuration.getSqlFragments().get(refid);
    // 克隆结果,防止影响老的
    Node result = nodeToInclude.getNode().cloneNode(true);
    return result;
  } catch (IllegalArgumentException e) {
    throw new IncompleteElementException("Could not find SQL statement to include with refid '" + refid + "'", e);
  }
}

2.3:解析selectKey标签

源码如下:

org.apache.ibatis.builder.xml.XMLStatementBuilder#processSelectKeyNodes
/*
id:statement 的id值,即id属性的值
parameterTypeClass:
langDriver:用于处理statement的sql语句和参数相关信息的语言驱动类
*/
private void processSelectKeyNodes(String id, Class<?> parameterTypeClass, LanguageDriver langDriver) {
  // 提示:该处为了方便调试,可以增加“selectKeyNodes.size() > 0”条件断点
  // 获取配置的所有<selectKey/>标签节点集合
  List<XNode> selectKeyNodes = context.evalNodes("selectKey");
  if (configuration.getDatabaseId() != null) {
    parseSelectKeyNodes(id, selectKeyNodes, parameterTypeClass, langDriver, configuration.getDatabaseId());
  }
  // 解析<selectKey/>标签
  // <202108091410>
  parseSelectKeyNodes(id, selectKeyNodes, parameterTypeClass, langDriver, null);
  // 移除<selectKey/>节点,因为已经解析完毕没有用了,所以移除
  removeSelectKeyNodes(selectKeyNodes);
}

<202108091410>处是解析<selectKey/>节点集合,源码如下:

org.apache.ibatis.builder.xml.XMLStatementBuilder#parseSelectKeyNodes
private void parseSelectKeyNodes(String parentId, List<XNode> list, Class<?> parameterTypeClass, LanguageDriver langDriver, String skRequiredDatabaseId) {
  // 遍历所有的<selectKey/>节点
  for (XNode nodeToHandle : list) {
    // parentId+SELECT_KEY_SUFFIX,如insertByQueryParamter!selectKey,作为id值
    // 最终selectKey会映射为MappedStatment对象,注意这里可能配置有多个的情况,不同的
    // databaseId每个配置一个,但是只有符合要求的那个datbaseId才会被解析
    String id = parentId + SelectKeyGenerator.SELECT_KEY_SUFFIX;
    // 获取selectKey配置的databaseId属性
    String databaseId = nodeToHandle.getStringAttribute("databaseId");
    // 判断配置的databaseId和当前要求的是否匹配
    if (databaseIdMatchesCurrent(id, databaseId, skRequiredDatabaseId)) {
      // 解析单个的<selectKey/>节点
      // <202108091458>
      parseSelectKeyNode(id, nodeToHandle, parameterTypeClass, langDriver, databaseId);
    }
  }
}

<202108091458>处源码如下:

org.apache.ibatis.builder.xml.XMLStatementBuilder#parseSelectKeyNode
private void parseSelectKeyNode(String id, XNode nodeToHandle, Class<?> parameterTypeClass, LanguageDriver langDriver, String databaseId) {
  // 获取resultType属性值
  String resultType = nodeToHandle.getStringAttribute("resultType");
  // 解析resultType属性值为对应的class类型
  Class<?> resultTypeClass = resolveClass(resultType);
  // 获取statementType属性值,默认为PREPARED,即用JDBC的PreparedStatement接口API
  StatementType statementType = StatementType.valueOf(nodeToHandle.getStringAttribute("statementType", StatementType.PREPARED.toString()));
  // 获取keyProperty属性值
  String keyProperty = nodeToHandle.getStringAttribute("keyProperty");
  // 获取keyColumn属性值
  String keyColumn = nodeToHandle.getStringAttribute("keyColumn");
  // 获取order属性值
  boolean executeBefore = "BEFORE".equals(nodeToHandle.getStringAttribute("order", "AFTER"));

  // 各种属性的默认值,用于创建MappedStatement对象
  boolean useCache = false;
  boolean resultOrdered = false;
  KeyGenerator keyGenerator = new NoKeyGenerator();
  Integer fetchSize = null;
  Integer timeout = null;
  boolean flushCache = false;
  String parameterMap = null;
  String resultMap = null;
  ResultSetType resultSetTypeEnum = null;
  // 创建SqlSource对象,这是一个用于封装statement内部定义信息的接口,可以理解为封装sql语句信息
  SqlSource sqlSource = langDriver.createSqlSource(configuration, nodeToHandle, parameterTypeClass);
  // statement的类型,默认是select
  SqlCommandType sqlCommandType = SqlCommandType.SELECT;
  // 创建MappedStatement,并添加到configuration中
  builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
      fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
      resultSetTypeEnum, flushCache, useCache, resultOrdered,
      keyGenerator, keyProperty, keyColumn, databaseId, langDriver, null);
  // 添加命名空间前缀,如果需要的话,最终${namespace}.${id}
  id = builderAssistant.applyCurrentNamespace(id, false);
  // 通过当前id获取创建好的MappedStatement,即<selectKey/>标签信息
  MappedStatement keyStatement = configuration.getMappedStatement(id, false);
  // 创建KeyGenerator 并添加到configuration中
  /*
  public void addKeyGenerator(String id, KeyGenerator keyGenerator) {
    // protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<KeyGenerator>("Key Generators collection");
    keyGenerators.put(id, keyGenerator);
  }
  */
  configuration.addKeyGenerator(id, new SelectKeyGenerator(keyStatement, executeBefore));
}

2.4:构建MappedStatement

源码如下:

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) {
  // 如果此时cache-ref还没有解析,则异常
  if (unresolvedCacheRef) throw new IncompleteElementException("Cache-ref not yet resolved");
  // 添加命名空间前缀,如果需要的话,格式${namespace}+${id}
  id = applyCurrentNamespace(id, false);
  // SqlCommandType为statement类型的枚举类,源码如下:
  /*
  public enum SqlCommandType {
	  UNKNOWN, INSERT, UPDATE, DELETE, SELECT;
  }
  */
  boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
  // 创建MappedStatement的构造器对象
  MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType);
  // 设置构造器的各种属性
  statementBuilder.resource(resource);
  statementBuilder.fetchSize(fetchSize);
  statementBuilder.statementType(statementType);
  statementBuilder.keyGenerator(keyGenerator);
  statementBuilder.keyProperty(keyProperty);
  statementBuilder.keyColumn(keyColumn);
  statementBuilder.databaseId(databaseId);
  statementBuilder.lang(lang);
  statementBuilder.resultOrdered(resultOrdered);
  statementBuilder.resulSets(resultSets);
  setStatementTimeout(timeout, statementBuilder);
  // 获取ParameterMap并设置到MappedStatement的构造器中
  setStatementParameterMap(parameterMap, parameterType, statementBuilder);
  // 获取ResultMap并设置的MappedStatement的构造器中
  setStatementResultMap(resultMap, resultType, resultSetType, statementBuilder);
  // 设置cache-ref到MappedStatment中
  setStatementCache(isSelect, flushCache, useCache, currentCache, statementBuilder);
  // 通过构造器构造MappedStatement
  // <202108091754>
  MappedStatement statement = statementBuilder.build();
  configuration.addMappedStatement(statement);
  return statement;
}

<202108091754>处的MappedStatement对象是用来封装<insert/>,<delete/>,<update/>,<select/>标签的,另外就是,<selectKey/>标签也会解析成MappedStatement,因此一共是5个标签。

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐