MyBatis源码笔记(六) -- 解析SQL语句节点封装为MappedStatement

MyBatis解析XML的SQL语句节点或注解@Select等,最终会封装成MappedStatement对象,存进configuration里,本篇分析XML解析方式

从XMLMapperBuilder类的buildStatementFromContext方法开始

private void buildStatementFromContext(List<XNode> list) {
    //如果有指定数据库标志
    if (configuration.getDatabaseId() != null) {
      buildStatementFromContext(list, configuration.getDatabaseId());
    }
    buildStatementFromContext(list, null);
}

private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
    for (XNode context : list) {
      //构建所有语句,一个mapper下可以有很多select
      //语句比较复杂,核心都在这里面,所以调用XMLStatementBuilder
      final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
      try {
          //核心XMLStatementBuilder.parseStatementNode
        statementParser.parseStatementNode();
      } catch (IncompleteElementException e) {
          //如果出现SQL语句不完整,把它记下来,塞到configuration去
        configuration.addIncompleteStatement(statementParser);
      }
    }
}

上面如果有指定数据库标志,那会调用两次buildStatementFromContext方法,而该方法的核心在于statementParser的parseStatementNode方法。

之所以在buildStatementFromContext方法,是因为在parseStatementNode方法中,会调databaseIdMatchesCurrent进行判断过滤是否进行MappedStatement构建。在指定数据库标识的情况下,如果SQL语句节点的databaseId属性也契合的话,优先级最高。

/**
 * @param id SQL语句节点的id属性
 * @param databaseId 节点中databaseId属性
 * @param requiredDatabaseId configuration中的变量;数据库标志
 * @return
 */
private boolean databaseIdMatchesCurrent(String id, String databaseId, String requiredDatabaseId) {
    if (requiredDatabaseId != null) {
      if (!requiredDatabaseId.equals(databaseId)) {
        return false;//不一致返回false
      }
    } else {
      if (databaseId != null) {
		//存在databaseId属性直接返回false,因为上面的if已经做了判断
        return false;
      }
      // skip this statement if there is a previous one with a not null databaseId
		//保证是带命名空间前缀
      id = builderAssistant.applyCurrentNamespace(id, false);
		//检查是否存在相同id的MappedStatement(不包括没完整构建出来的)
      if (this.configuration.hasStatement(id, false)) {
		//存在,那就拿到之前的
        MappedStatement previous = this.configuration.getMappedStatement(id, false); // issue #2
		//如果之前的存在databaseId属性,那就返回false
		//因为之前的肯定通过了上面和requiredDatabaseId比较的判断
        if (previous.getDatabaseId() != null) {
          return false;
        }
      }
    }
    return true;
 }

下面开始看核心解析方法:

//解析语句(select|insert|update|delete)
//<select
//  id="selectPerson"
//  parameterType="int"
//  parameterMap="deprecated"
//  resultType="hashmap"
//  resultMap="personResultMap"
//  flushCache="false"
//  useCache="true"
//  timeout="10000"
//  fetchSize="256"
//  statementType="PREPARED"
//  resultSetType="FORWARD_ONLY">
//  SELECT * FROM PERSON WHERE ID = #{id}
//</select>
public void parseStatementNode() {
    String id = context.getStringAttribute("id");
    String databaseId = context.getStringAttribute("databaseId");
    //如果databaseId不匹配,退出,上面已列出
    if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
      return;
    }
    //暗示驱动程序每次批量返回的结果行数
    Integer fetchSize = context.getIntAttribute("fetchSize");
    //超时时间
    Integer timeout = context.getIntAttribute("timeout");
    //引用外部 parameterMap,已废弃
    String parameterMap = context.getStringAttribute("parameterMap");
    //参数类型
    String parameterType = context.getStringAttribute("parameterType");
    Class<?> parameterTypeClass = resolveClass(parameterType);
    //引用外部的 resultMap(高级功能)
    String resultMap = context.getStringAttribute("resultMap");
    //结果类型
    String resultType = context.getStringAttribute("resultType");
    //脚本语言,mybatis3.2的新功能
    String lang = context.getStringAttribute("lang");
    //得到语言驱动,默认是XML语言驱动
    LanguageDriver langDriver = getLanguageDriver(lang);
	//拿到返回类型的Class
    Class<?> resultTypeClass = resolveClass(resultType);
    //结果集类型,是对jdbc的resultset不同处理,FORWARD_ONLY|SCROLL_SENSITIVE|SCROLL_INSENSITIVE 中的一种
    String resultSetType = context.getStringAttribute("resultSetType");
    //语句类型, STATEMENT|PREPARED|CALLABLE 的一种,默认是预编译型
    StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
	//mybatis把FORWARD_ONLY|SCROLL_SENSITIVE|SCROLL_INSENSITIVE封装成了枚举类型
    ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
    //获取命令类型(select|insert|update|delete)
    String nodeName = context.getNode().getNodeName();
    SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
    //如果是查询默认不清缓存
    boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
    //是否要缓存select结果
    boolean useCache = context.getBooleanAttribute("useCache", isSelect);
    //仅针对嵌套结果 select 语句适用:如果为 true,就是假设包含了嵌套结果集或是分组了,这样的话当返回一个主结果行的时候,就不会发生有对前面结果集的引用的情况。
    //这就使得在获取嵌套的结果集的时候不至于导致内存不够用。默认值:false。 
    boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);

    //1.解析之前先解析<include>SQL片段,拼装到当前的sql中,如:
    XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
    includeParser.applyIncludes(context.getNode());

    //2.解析之前先解析<selectKey>
    processSelectKeyNodes(id, parameterTypeClass, langDriver);
    
    // 上面解析完后,会把<include>、<selectKey>节点删掉
    //3.解析成SqlSource,一般是DynamicSqlSource
    SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
    String resultSets = context.getStringAttribute("resultSets");
    //(仅对 insert 有用) 标记一个属性, MyBatis 会通过 getGeneratedKeys 或者通过 insert 语句的 selectKey 子元素设置它的值
    String keyProperty = context.getStringAttribute("keyProperty");
    //(仅对 insert 有用) 标记一个属性, MyBatis 会通过 getGeneratedKeys 或者通过 insert 语句的 selectKey 子元素设置它的值
    String keyColumn = context.getStringAttribute("keyColumn");
    KeyGenerator keyGenerator;
    String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
    keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
	//4.主键生成器赋值
    if (configuration.hasKeyGenerator(keyStatementId)) {
		//如果已存在改keyStatementId的主键生成器,则直接复制
      keyGenerator = configuration.getKeyGenerator(keyStatementId);
    } else {
		//如果属性值没有useGeneratedKeys,就根据isUseGeneratedKeys配置和是否是插入语句决定
      keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
          configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
          ? new Jdbc3KeyGenerator() : new NoKeyGenerator();
    }

	//5.去调助手类,这里构建一个MappedStatement存入configuration
    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
        resultSetTypeEnum, flushCache, useCache, resultOrdered, 
        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}

上面代码有点长,前面部分是属性获取,然后解析<include>、<selectKey>节点并删除,接着解析封装一个SqlSource对象,再接着就是主键生成器赋值,最后构建MappedStatement。

1.解析之前先解析<include>节点

略  ̄□ ̄||

2. <selectKey> 节点解析

private void processSelectKeyNodes(String id, Class<?> parameterTypeClass, LanguageDriver langDriver) {
    List<XNode> selectKeyNodes = context.evalNodes("selectKey");
    if (configuration.getDatabaseId() != null) {
      parseSelectKeyNodes(id, selectKeyNodes, parameterTypeClass, langDriver, configuration.getDatabaseId());
    }
    parseSelectKeyNodes(id, selectKeyNodes, parameterTypeClass, langDriver, null);
    removeSelectKeyNodes(selectKeyNodes);//删除节点
 	}

private void parseSelectKeyNodes(String parentId, List<XNode> list, Class<?> parameterTypeClass, LanguageDriver langDriver, String skRequiredDatabaseId) {
    for (XNode nodeToHandle : list) {
      String id = parentId + SelectKeyGenerator.SELECT_KEY_SUFFIX;
      String databaseId = nodeToHandle.getStringAttribute("databaseId");
      if (databaseIdMatchesCurrent(id, databaseId, skRequiredDatabaseId)) {
        parseSelectKeyNode(id, nodeToHandle, parameterTypeClass, langDriver, databaseId);
      }
    }
}

上面调了两次parseSelectKeyNodes,套路跟文章开头那里的类似,真正解析在parseSelectKeyNode方法中

private void parseSelectKeyNode(String id, XNode nodeToHandle, Class<?> parameterTypeClass, LanguageDriver langDriver, String databaseId) {
    String resultType = nodeToHandle.getStringAttribute("resultType");
    Class<?> resultTypeClass = resolveClass(resultType);
    StatementType statementType = StatementType.valueOf(nodeToHandle.getStringAttribute("statementType", StatementType.PREPARED.toString()));
    String keyProperty = nodeToHandle.getStringAttribute("keyProperty");
    String keyColumn = nodeToHandle.getStringAttribute("keyColumn");
    //默认是后置设置id
    boolean executeBefore = "BEFORE".equals(nodeToHandle.getStringAttribute("order", "AFTER"));

    //defaults
    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;
	//2.1 解析节点封装为sqlSource对象
    SqlSource sqlSource = langDriver.createSqlSource(configuration, nodeToHandle, parameterTypeClass);
	//设SQL类型为SELECT
    SqlCommandType sqlCommandType = SqlCommandType.SELECT;
	//2.2构建一个MappedStatement
    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
        resultSetTypeEnum, flushCache, useCache, resultOrdered,
        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, null);

    id = builderAssistant.applyCurrentNamespace(id, false);

    MappedStatement keyStatement = configuration.getMappedStatement(id, false);
	//2.3添加一个SelectKeyGenerator
    configuration.addKeyGenerator(id, new SelectKeyGenerator(keyStatement, executeBefore));
 }

解析流程是:解析节点封装为sqlSource对象,然后构建一个MappedStatement,接着在为该添加一个SelectKeyGenerator

2.1解析节点封装为sqlSource对象

public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
	//用XML脚本构建器解析
    XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
    return builder.parseScriptNode();
 	}

public SqlSource parseScriptNode() {
	//解析XML的SQL语言封装成SqlNode,放进集合
    List<SqlNode> contents = parseDynamicTags(context);
    MixedSqlNode rootSqlNode = new MixedSqlNode(contents);
    SqlSource sqlSource = null;
    if (isDynamic) {//动态SQL封装为DynamicSqlSource
      sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
    } else {//静态SQL封装为RawSqlSource
      sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
    }
    return sqlSource;
 }

上面解析SQL语句时,只要sql语句有${}、或者带有、等标签,isDynamic标志会设为true。

List<SqlNode> parseDynamicTags(XNode node) {
    List<SqlNode> contents = new ArrayList<SqlNode>();
    NodeList children = node.getNode().getChildNodes();
    for (int i = 0; i < children.getLength(); i++) {
      XNode child = node.newXNode(children.item(i));
		//如果是XML节点文本内容
      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()) {//这里主要判断sql语句中是否有${}
          contents.add(textSqlNode);
          isDynamic = true;
        } else {
			//不是动态SQL,封装成StaticTextSqlNode
          contents.add(new StaticTextSqlNode(data));
        }
      } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
		//这里就是动态语言节点解析,如<if>、<where>等
        String nodeName = child.getNode().getNodeName();
        NodeHandler handler = nodeHandlers(nodeName);
        if (handler == null) {
          throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
        }
        handler.handleNode(child, contents);
        isDynamic = true;
      }
    }
    return contents;
 }

sqlnode_impl

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

SqlNode是个接口,实现类有十个,只有一个方法,该方法是对SQL语句作进一步的处理,其中StaticTextSqlNode表示不是动态SQL类型,

2.2构建一个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) {
    
    if (unresolvedCacheRef) {
      throw new IncompleteElementException("Cache-ref not yet resolved");
    }
    
    //为id加上namespace前缀
    id = applyCurrentNamespace(id, false);
    //是否是select语句
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;

    //又是建造者模式
    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);

    //参数映射
    setStatementParameterMap(parameterMap, parameterType, statementBuilder);
    //结果映射
    setStatementResultMap(resultMap, resultType, resultSetType, statementBuilder);
    setStatementCache(isSelect, flushCache, useCache, currentCache, statementBuilder);

    MappedStatement statement = statementBuilder.build();
    //建造好调用configuration.addMappedStatement
    configuration.addMappedStatement(statement);
    return statement;
  }

MappedStatement记录着SQL节点的详细信息,最终存在configuration中

2.3添加一个SelectKeyGenerator

平时使用selectKey例子如下

<selectKey resultType="java.lang.Long" keyProperty="id" order="BEFORE" >
     SELECT LAST_INSERT_ID()
</selectKey>

public interface KeyGenerator {
  //定了2个回调方法,processBefore,processAfter
  void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter);

  void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter);
}

KeyGenerator会跟在SQL执行前后分别调用processBefore、processAfter,其中parameter是我们函数的入参

看看SelectKeyGenerator如何实现两个方法

	  @Override
	  public void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
	    if (executeBefore) {
	      processGeneratedKeys(executor, ms, parameter);
	    }
	  }
	
	  @Override
	  public void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
	    if (!executeBefore) {
	      processGeneratedKeys(executor, ms, parameter);
	    }
	  }

最终都是调用processGeneratedKeys

private void processGeneratedKeys(Executor executor, MappedStatement ms, Object parameter) {
    try {
      if (parameter != null && keyStatement != null && keyStatement.getKeyProperties() != null) {
        String[] keyProperties = keyStatement.getKeyProperties();
        final Configuration configuration = ms.getConfiguration();
		//
        final MetaObject metaParam = configuration.newMetaObject(parameter);
        if (keyProperties != null) {
          //这里创建一个SimpleExecutor
          Executor keyExecutor = configuration.newExecutor(executor.getTransaction(), ExecutorType.SIMPLE);
		  //执行语句
          List<Object> values = keyExecutor.query(keyStatement, parameter, RowBounds.DEFAULT, Executor.NO_RESULT_HANDLER);
          if (values.size() == 0) {
            throw ...
          } else if (values.size() > 1) {
            throw ...
          } else {//结果数量为1才正确
            MetaObject metaResult = configuration.newMetaObject(values.get(0));
            if (keyProperties.length == 1) {
              if (metaResult.hasGetter(keyProperties[0])) {
				//返回结果是个对象,判断是否有getter方法
                setValue(metaParam, keyProperties[0], metaResult.getValue(keyProperties[0]));
              } else {
                //返回结果是个基本类型, 设置id属性值
                setValue(metaParam, keyProperties[0], values.get(0));
              }
            } else {
				//多个属性处理
                handleMultipleProperties(keyProperties, metaParam, metaResult);
            }
          }
        }
      }
    } catch ...
 }

3.解析成SqlSource

参考2.1

4.主键生成器赋值

略  ̄□ ̄||

5.去调助手类,这里构建一个MappedStatement

参考2.2

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值