MyBatis源码分析之用法详解

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u012734441/article/details/86588597

MyBatis源码分析之

在上一篇文章中讲到MyBatis的#{paras}和${paras}用法,在里面提到在解析sql组装成SqlSource对象时,会判断当前sql是否是动态类型,然后里面有一个对sql中是否含有**

1.


与之前相同,讲到这里还是先讲一下

@ResultMap("BaseResultMap")
@Select("<script>" +
            "select * from user " +
            "<where>" +
            "<if test=\"age != null\"> age = #{age}</if>" +
            "</where>" +
            "</script>")
List<User> getUser4(@Param("age") Integer age);

判断传进去的age字段是否为空,为空则查询全部,不为空则查询对应字段值下的记录,这个动态sql其实写的挺麻烦的,还要转义,注意script的结束等等,我觉得还不如xml写起来方便。

调用程序为:

List<User> users = userMapper.getUser4(null);
System.out.println(users);
System.out.println();
List<User> users2 = userMapper.getUser4(25);
System.out.println(users2);

查询的结果和预料中差不多,users中为全部记录,users2中为1条记录。

在这里插入图片描述

2. 源码解析

上述中讲述了

@Override
  public SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType) {
    // issue #3
    if (script.startsWith("<script>")) {
      XPathParser parser = new XPathParser(script, false, configuration.getVariables(), new XMLMapperEntityResolver());
      return createSqlSource(configuration, parser.evalNode("/script"), parameterType);
    } else {
      // issue #127
      script = PropertyParser.parse(script, configuration.getVariables());
      TextSqlNode textSqlNode = new TextSqlNode(script);
      if (textSqlNode.isDynamic()) {
        return new DynamicSqlSource(configuration, textSqlNode);
      } else {
        return new RawSqlSource(configuration, script, parameterType);
      }
    }
  }

可以转到createSqlSource方法看一看。

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

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

在parseScriptNode中进行了具体的sql类型判断,其中如果isDynamic类型为true,则返回DynamicSqlSource,否则返回RawSqlSource,isDynamic在parseDynamicTags方法中进行初始化,进这个方法看看。

protected MixedSqlNode 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));
      if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
        String data = child.getStringBody("");
        TextSqlNode textSqlNode = new TextSqlNode(data);
        if (textSqlNode.isDynamic()) {
          contents.add(textSqlNode);
          isDynamic = true;
        } else {
          contents.add(new StaticTextSqlNode(data));
        }
      } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
        String nodeName = child.getNode().getNodeName();
        NodeHandler handler = nodeHandlerMap.get(nodeName);
        if (handler == null) {
          throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
        }
        handler.handleNode(child, contents);
        isDynamic = true;
      }
    }
    return new MixedSqlNode(contents);
  }

这个方法用来解析节点,调试一下看下。

在这里插入图片描述

此时node节点内容与我们之前查看的sql相同,中间分析的过程非常多,比较复杂,这就不再单独看了,可以直接看这个方法的返回。

在这里插入图片描述

这里返回的rootSQLNode中contents对象有两个,一个为固定查询sql,一个为条件sql,最后在这返回DynamicSqlSource对象。

最后我看执行到executor中的query时的代码。

@Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameter);
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
 }

调试到getBoundSql处,拿到BoundSql查看,两次分别的结果为:

在传入age为null时,返回为不带查询条件的sql。

在age不为null时,此时返回的sql为:

在这里插入图片描述

此时就有age查询条件。我们进getBoundSql方法中查看具体细节。

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

在调试代码时可知此时获得的boundSql对象已经是组装完成的sql。

在这里插入图片描述

此时已经将sql组装完成了,我们继续进SqlSource方法中看看。

@Override
  public BoundSql getBoundSql(Object parameterObject) {
    DynamicContext context = new DynamicContext(configuration, parameterObject);
    rootSqlNode.apply(context);
    SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
    Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
    SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
    for (Map.Entry<String, Object> entry : context.getBindings().entrySet()) {
      boundSql.setAdditionalParameter(entry.getKey(), entry.getValue());
    }
    return boundSql;
  }

在调用rootSqlNode.apply方法,在前面分析知这里的SqlNode是MixedSqlNode.

@Override
  public boolean apply(DynamicContext context) {
    for (SqlNode sqlNode : contents) {
      sqlNode.apply(context);
    }
    return true;
  }

此时的contents对象就是两个,一个为固定sql,一个是where条件,如下图:

在这里插入图片描述

第一个sqlNode没啥说的,固定sql调用append方就可以了,如下:

@Override
  public boolean apply(DynamicContext context) {
    context.appendSql(text);
    return true;
  }

进入where条件的SQLNode,where条件没有实现apply方法,此处调用的为父类TrimSqlNode类的apply方法,apply方法为:

@Override
  public boolean apply(DynamicContext context) {
    FilteredDynamicContext filteredDynamicContext = new FilteredDynamicContext(context);
    boolean result = contents.apply(filteredDynamicContext);
    filteredDynamicContext.applyAll();
    return result;
  }

在调用contents.apply(filteredDynamicContext)方法时还要再回到MixSqlNode处,此时调用就是IfSqlNode。

在这里插入图片描述

此时的动态sql就是where中的条件了,然后后续filteredDynamicContext.applyAll()中的操作就是给sql加上where前缀,这里就不再分析了,有兴趣的可以自己看看。


Script用法比较简单,我原想原理应该也是和之前差不多,没想到分析进来这么多处理和判断,可以说看得晕头转向,此处先留个记号,以后有空还要再回来仔细分析一下。

猜你喜欢

转载自blog.csdn.net/u012734441/article/details/86588597