mybatis源码(七)mybatis动态sql的解析过程下篇

mybatis源码(七)mybatis动态sql的解析过程下篇

  mybatis的MapperStatement的创建过程中,进行的动态sql解析

  XMLStatementBuilder.parseStatementNode()

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

    if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
      return;
    }
    // 解析<select|update|delete|insert>标签属性
    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");
    // 获取LanguageDriver对象
    String lang = context.getStringAttribute("lang");
    LanguageDriver langDriver = getLanguageDriver(lang);
    // 获取Mapper返回结果类型Class对象
    Class<?> resultTypeClass = resolveClass(resultType);
    String resultSetType = context.getStringAttribute("resultSetType");
    // 默认Statement类型为PREPARED
    StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType",
            StatementType.PREPARED.toString()));
    ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);

    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>标签内容,替换为<sql>标签定义的SQL片段
    XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
    includeParser.applyIncludes(context.getNode());

    // 解析<selectKey>标签
    processSelectKeyNodes(id, parameterTypeClass, langDriver);
    
    // 通过LanguageDriver解析SQL内容,生成SqlSource对象
    SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
    String resultSets = context.getStringAttribute("resultSets");
    String keyProperty = context.getStringAttribute("keyProperty");
    String keyColumn = context.getStringAttribute("keyColumn");
    KeyGenerator keyGenerator;
    String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
    keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
    // 获取主键生成策略
    if (configuration.hasKeyGenerator(keyStatementId)) {
      keyGenerator = configuration.getKeyGenerator(keyStatementId);
    } else {
      keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
          configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
          ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
    }

    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
        resultSetTypeEnum, flushCache, useCache, resultOrdered, 
        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
  }

其中是从这里42行的 LanguageDriver 开始的

SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
源码如下:
public class XMLLanguageDriver implements LanguageDriver {

  @Override
  public ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
    return new DefaultParameterHandler(mappedStatement, parameterObject, boundSql);
  }
  @Override
  public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
    // 该方法用于解析XML文件中配置的SQL信息
    // 创建XMLScriptBuilder对象
    XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
    // 调用 XMLScriptBuilder对象parseScriptNode()方法解析SQL资源
    return builder.parseScriptNode();
  }

  @Override
  public SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType) {
    // 该方法用于解析Java注解中配置的SQL信息
    // 字符串以<script>标签开头,则以XML方式解析
    if (script.startsWith("<script>")) {
      XPathParser parser = new XPathParser(script, false, configuration.getVariables(), new XMLMapperEntityResolver());
      return createSqlSource(configuration, parser.evalNode("/script"), parameterType);
    } else {
      // 解析SQL配置中的全局变量
      script = PropertyParser.parse(script, configuration.getVariables());
      TextSqlNode textSqlNode = new TextSqlNode(script);
      // 如果SQL中是否仍包含${}参数占位符,则返回DynamicSqlSource实例,否则返回RawSqlSource
      if (textSqlNode.isDynamic()) {
        return new DynamicSqlSource(configuration, textSqlNode);
      } else {
        return new RawSqlSource(configuration, script, parameterType);
      }
    }
  }

}

其中 XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType); 的过程。做了标签和对应处理器的初始化工作

public class XMLScriptBuilder extends BaseBuilder {

  private final XNode context;
  private boolean isDynamic;
  private final Class<?> parameterType;
  private final Map<String, NodeHandler> nodeHandlerMap = new HashMap<String, NodeHandler>();

  public XMLScriptBuilder(Configuration configuration, XNode context) {
    this(configuration, context, null);
  }

  public XMLScriptBuilder(Configuration configuration, XNode context, Class<?> parameterType) {
    super(configuration);
    this.context = context;
    this.parameterType = parameterType;
    initNodeHandlerMap();
  }


  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());
  }
View Code
XMLScriptBuilder类parseScriptNode()方法开始了解析工作
  public SqlSource parseScriptNode() {
    // 调用parseDynamicTags()方法將SQL配置转换为SqlNode对象
    MixedSqlNode rootSqlNode = parseDynamicTags(context);
    SqlSource sqlSource = null;
    // 判断Mapper SQL配置中是否包含动态SQL元素,如果是创建DynamicSqlSource对象,否则创建RawSqlSource对象
    if (isDynamic) {
      sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
    } else {
      sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
    }
    return sqlSource;
  }

如上面的代码所示,在XMLScriptBuilder类的parseScriptNode()方法中,调用parseDynamicTags()方法将SQL 配置转换为SqlNode对象,然后判断SQL配置是否为动态SQL,如果为动态SQL,则创建DynamicSqlSource对象,否则创建RawSqlSource对象。需要注意的是,MyBatis中判断SQL配置是否属于动态SQL的标准是SQL配置是否包含<if>、<where>、 <trim> 等元素或者${}参数占位符。DynamicSqlSource这里只是创建了一个对象,并没有做解析工作。解析工作是在执行sql语句的时候完成的。RawSqlSource这里既创建了对象,又实现了sql的解析

解析部分源代码如下:

  protected MixedSqlNode parseDynamicTags(XNode node) {
    List<SqlNode> contents = new ArrayList<SqlNode>();
    NodeList children = node.getNode().getChildNodes();
    // 对XML子元素进行遍历
    for (int i = 0; i < children.getLength(); i++) {
      XNode child = node.newXNode(children.item(i));
      // 如果子元素为SQL文本内容,则使用TextSqlNode描述该节点
      if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
        String data = child.getStringBody("");
        TextSqlNode textSqlNode = new TextSqlNode(data);
        // 判断SQL文本中包含${}参数占位符,则为动态SQL
        if (textSqlNode.isDynamic()) {
          contents.add(textSqlNode);
          isDynamic = true;
        } else {
          // 如果SQL文本中不包含${}参数占位符,则不是动态SQL
          contents.add(new StaticTextSqlNode(data));
        }
      } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) {
        // 如果子元素为<if>、<where>等标签,则使用对应的NodeHandler处理
        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);
  }

其中涉及到了NodeHandler处理器在上一篇中已经提到了。这是一个接口,提供了8种实现类。每种处理器用于处理对应的sql的动态标签。例如IfHandler用于处理动态sql配置中的<if>标签。负责将<if>标签内容转换为ifSqlNode对象。nodeHandlerMap是在XMLScriptBuilder创建的时候,就把标签和对应的处理器初始化完成了

NodeHandler接口是XMLScriptBuilder内部的一个接口,源码实现如下:

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

  private class BindHandler implements NodeHandler {
    public BindHandler() {
      // Prevent Synthetic Access
    }

    @Override
    public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
      final String name = nodeToHandle.getStringAttribute("name");
      final String expression = nodeToHandle.getStringAttribute("value");
      final VarDeclSqlNode node = new VarDeclSqlNode(name, expression);
      targetContents.add(node);
    }
  }

  private class TrimHandler implements NodeHandler {
    public TrimHandler() {
      // Prevent Synthetic Access
    }

    @Override
    public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
      MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
      String prefix = nodeToHandle.getStringAttribute("prefix");
      String prefixOverrides = nodeToHandle.getStringAttribute("prefixOverrides");
      String suffix = nodeToHandle.getStringAttribute("suffix");
      String suffixOverrides = nodeToHandle.getStringAttribute("suffixOverrides");
      TrimSqlNode trim = new TrimSqlNode(configuration, mixedSqlNode, prefix, prefixOverrides, suffix, suffixOverrides);
      targetContents.add(trim);
    }
  }

  private class WhereHandler implements NodeHandler {
    public WhereHandler() {
      // Prevent Synthetic Access
    }

    @Override
    public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
      MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
      WhereSqlNode where = new WhereSqlNode(configuration, mixedSqlNode);
      targetContents.add(where);
    }
  }

  private class SetHandler implements NodeHandler {
    public SetHandler() {
      // Prevent Synthetic Access
    }

    @Override
    public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
      MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
      SetSqlNode set = new SetSqlNode(configuration, mixedSqlNode);
      targetContents.add(set);
    }
  }

  private class ForEachHandler implements NodeHandler {
    public ForEachHandler() {
      // Prevent Synthetic Access
    }

    @Override
    public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
      // 首先调用parseDynamicTags()方法解析<foreach>标签子元素
      MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
      String collection = nodeToHandle.getStringAttribute("collection");
      String item = nodeToHandle.getStringAttribute("item");
      String index = nodeToHandle.getStringAttribute("index");
      String open = nodeToHandle.getStringAttribute("open");
      String close = nodeToHandle.getStringAttribute("close");
      String separator = nodeToHandle.getStringAttribute("separator");
      ForEachSqlNode forEachSqlNode = new ForEachSqlNode(configuration, mixedSqlNode, collection, index, item, open, close, separator);
      targetContents.add(forEachSqlNode);
    }
  }

  private class IfHandler implements NodeHandler {
    public IfHandler() {
      // Prevent Synthetic Access
    }

    @Override
    public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
      // 继续调用parseDynamicTags()方法解析<if>标签中的子节点
      MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
      // 获取<if>标签test属性
      String test = nodeToHandle.getStringAttribute("test");
      // 创建IfSqlNode对象
      IfSqlNode ifSqlNode = new IfSqlNode(mixedSqlNode, test);
      // 將IfSqlNode对象添加到List中
      targetContents.add(ifSqlNode);
    }
  }

  private class OtherwiseHandler implements NodeHandler {
    public OtherwiseHandler() {
      // Prevent Synthetic Access
    }

    @Override
    public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
      MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
      targetContents.add(mixedSqlNode);
    }
  }

  private class ChooseHandler implements NodeHandler {
    public ChooseHandler() {
      // Prevent Synthetic Access
    }

    @Override
    public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
      List<SqlNode> whenSqlNodes = new ArrayList<SqlNode>();
      List<SqlNode> otherwiseSqlNodes = new ArrayList<SqlNode>();
      handleWhenOtherwiseNodes(nodeToHandle, whenSqlNodes, otherwiseSqlNodes);
      SqlNode defaultSqlNode = getDefaultSqlNode(otherwiseSqlNodes);
      ChooseSqlNode chooseSqlNode = new ChooseSqlNode(whenSqlNodes, defaultSqlNode);
      targetContents.add(chooseSqlNode);
    }

    private void handleWhenOtherwiseNodes(XNode chooseSqlNode, List<SqlNode> ifSqlNodes, List<SqlNode> defaultSqlNodes) {
      List<XNode> children = chooseSqlNode.getChildren();
      for (XNode child : children) {
        String nodeName = child.getNode().getNodeName();
        NodeHandler handler = nodeHandlerMap.get(nodeName);
        if (handler instanceof IfHandler) {
          handler.handleNode(child, ifSqlNodes);
        } else if (handler instanceof OtherwiseHandler) {
          handler.handleNode(child, defaultSqlNodes);
        }
      }
    }

    private SqlNode getDefaultSqlNode(List<SqlNode> defaultSqlNodes) {
      SqlNode defaultSqlNode = null;
      if (defaultSqlNodes.size() == 1) {
        defaultSqlNode = defaultSqlNodes.get(0);
      } else if (defaultSqlNodes.size() > 1) {
        throw new BuilderException("Too many default (otherwise) elements in choose statement.");
      }
      return defaultSqlNode;
    }
  }

在IfHandler类的handleNode()方法中会继续调用XMLScriptBuilder类的parseDynamicTags()方法完成<if>标签子节点的解析,将子节点转换为MixedSqlNode对象,然后获取<if>标签test属性对应的OGNL表达式,接着创建IfSq|Node对象并添加到List对象中。parseDynamicTags()方法的内容上面已经分析过了,该方法中会获取当前节点的所有子节点,如果子节点内容为动态SQL标签,继续调用动态SQL标签对应的NodeHandler进行处理,这样就"递归”地完成了所有动态SQL标签的解析。

上面说了动态sql配置解析为SqlNode的过程,那么何时调用mapper,根据传入的参数动态生成sql语句的呢

继续从XMLScriptBuilder.parseScriptNode()方法开始,代码执行完这个方法之后,sqlSource对象就已经产生了

动态SQL标签解析完成后,将解析后生成的SqlNode对象封装在SqlSource对象中。MyBatis中的MappedStatement用于描述Mapper中的SQL配置,SqlSource创建完毕后,最终会存放在MappedStatement对象的sqlSource属性中,
Executor组件操作数据库时,会调用MappedStatement对象的getBoundSql()方法获取BoundSq|对象,部分源代码如下:

public final class MappedStatement {
private Cache cache; // 二级缓存实例
  private SqlSource sqlSource; // 解析SQL语句生成的SqlSource实例
  private String resource; // Mapper资源路径
  private Configuration configuration; // Configuration对象的引用
  private KeyGenerator keyGenerator;  // 默认为Jdbc3KeyGenerator,即数据库自增主键,当配置了<selectKey>时,使用SelectKeyGenerator
  private boolean hasNestedResultMaps; // 是否有嵌套的ResultMap
  private Log statementLog; // 输出日志

  public BoundSql getBoundSql(Object parameterObject) {
    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    if (parameterMappings == null || parameterMappings.isEmpty()) {
      boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);
    }

    // check for nested result maps in parameter mappings (issue #30)
    for (ParameterMapping pm : boundSql.getParameterMappings()) {
      String rmId = pm.getResultMapId();
      if (rmId != null) {
        ResultMap rm = configuration.getResultMap(rmId);
        if (rm != null) {
          hasNestedResultMaps |= rm.hasNestedResultMaps();
        }
      }
    }

    return boundSql;
  }
}

如上面的代码所示,MappedStatement对象的getBoundSql()方法会调用SqlSource对象的getBoundSq()方法,这个过程就完成了SqlNode对象解析成SQL语句的过程。我们可以了解一下DynamicSqlSource类的getBoundSq()方法的实现,代码如下:

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上下文对象
    DynamicContext context = new DynamicContext(configuration, parameterObject);
    // 以DynamicContext对象作为参数调用SqlNode的apply()方法
    rootSqlNode.apply(context);
    // 创建SqlSourceBuilder对象
    SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
    Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
    // 调用DynamicContext的getSql()方法获取动态SQL解析后的SQL内容,
    // 然后调用SqlSourceBuilder的parse()方法对SQL内容做进一步处理,生成StaticSqlSource对象
    SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
    // 调用StaticSqlSource对象的getBoundSql()方法,获得BoundSql实例
    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
    // 將<bind>标签绑定的参数添加到BoundSql对象中
    for (Map.Entry<String, Object> entry : context.getBindings().entrySet()) {
      boundSql.setAdditionalParameter(entry.getKey(), entry.getValue());
    }
    return boundSql;
  }

}

SqlSourceBuilder类的parse()方法对动态SQL解析后的结果到底做了什么操作。该方法的代码如下:  

public class SqlSourceBuilder extends BaseBuilder {

  private static final String parameterProperties = "javaType,jdbcType,mode,numericScale,resultMap,typeHandler,jdbcTypeName";

  public SqlSourceBuilder(Configuration configuration) {
    super(configuration);
  }
  public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
    // ParameterMappingTokenHandler为Mybatis参数映射处理器,用于处理SQL中的#{}参数占位符
    ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
    // Token解析器,用于解析#{}参数
    GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
    // 调用GenericTokenParser对象的parse()方法將#{}参数占位符转换为?
    String sql = parser.parse(originalSql);
    return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
  }
  private static class ParameterMappingTokenHandler extends BaseBuilder implements TokenHandler {

到这里BoundSql的解析就完成了。例如:执行Executor的query方法时,就会根据MapperStatement获取BoundSql,开启动态sql解析过程,部分源码如下:

BaseExecutor.java

  @Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    // 获取BoundSql对象,BoundSql是对动态SQL解析生成的SQL语句和参数映射信息的封装
    BoundSql boundSql = ms.getBoundSql(parameter);
    // 创建CacheKey,用于缓存Key
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    // 调用重载的query()方法
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
 }

SqlSession是Executor组件的外观,目的是对外提供易于理解和使用的操作数据库的接口,也就是sqlSession调用查询的时候,就会执行上述逻辑

BaseBuidler是mybatis中xml解析的时候比较重要的抽象类

  

原文地址:https://www.cnblogs.com/yingxiaocao/p/13697455.html