MyBatis底层实现(三)复杂标签SQL的解析

本文深入探讨了MyBatis在处理复杂标签SQL时的解析流程,从加载config.xml文件到生成SqlSource,再到XMLLanguageDriver的createSqlSource方法。重点解析了XMLScriptBuilder的parseScriptNode方法,如何根据#和$执行不同的操作,并通过parseDynamicTags方法处理if、switch、foreach等标签,创建相应的IfSqlNode和ForEachNode对象。最后,详细阐述了如何通过DynamicSqlSource和RawSqlSource封装成可执行的BoundSql。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

先给出一段代码,类似于下面的复杂SQL,在解析过程中又是怎么执行的,调用了哪些API

<select id="getAll" parameterType="map" resultType="map">
    	<bind name="pattern" value="'%' + _parameter.getTitle() + '%'" />  
        select  userId,username,age  
        from user  
        where 1=1  
        <if test="userId != 0 and userId != null">  
            and userId=#{userId}  
        </if>
        <if test="username != '' and username != null">  
            and username like "%"#{username}"%"  
        </if>    
      	<if test="names.size>0 and names != null">  
            and username in  
            <foreach item="item" index="index" collection="names" open="("  
                separator="," close=")">  
                #{item}  
            </foreach>  
        </if> 
        <choose>
    		<when test="phonenumber != null">
      			AND phonenumber like #{phonenumber}
    		</when>
    		<when test="address != null">
      			AND address like #{address}
    		</when>
    		<otherwise>
      			AND featured = 1
    		</otherwise>
  		</choose> 
  		order by ${age} asc
    </select>

1)加载解析config.xml文件,并解析mapping节点

public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }

mapperElement(root.evalNode("mappers"));

2)org.apache.ibatis.builder.xml.XMLStatementBuilderparseStatementNode()方法生成SqlSource

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

3)org.apache.ibatis.scripting.xmltags.XMLLanguageDrivercreateSqlSource()方法

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

4)org.apache.ibatis.scripting.xmltags.XMLScriptBuilderparseScriptNode()方法对#和$进行判断和分别执行,调用parsedynamictags()方法解析标签

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

对于这块之前有讲解过,如果是$符,则直接执行DynamicSqlSource,否则执行RawSqlSource,接着做替换为占位符的调用,这里就不做说明了,主要是讲解复杂标签的问题,所以感兴趣的同学可以自己了解或者看我之前的分析博客

5)parseDynamicTags()方法判断sql的一些标签      解析判断标签 if switch  foreach

NodeHandler nodeHandlers(String nodeName) {
    Map<String, NodeHandler> map = new HashMap<String, NodeHandler>();
    map.put("trim", new TrimHandler());
    map.put("where", new WhereHandler());
    map.put("set", new SetHandler());
    map.put("foreach", new ForEachHandler());
    map.put("if", new IfHandler());
    map.put("choose", new ChooseHandler());
    map.put("when", new IfHandler());
    map.put("otherwise", new OtherwiseHandler());
    map.put("bind", new BindHandler());
    return map.get(nodeName);
  }

创建对应的IfSqlNode 和ForEachNode对象

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

    @Override
    public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
      List<SqlNode> contents = parseDynamicTags(nodeToHandle);
      MixedSqlNode mixedSqlNode = new MixedSqlNode(contents);
      String test = nodeToHandle.getStringAttribute("test");
      IfSqlNode ifSqlNode = new IfSqlNode(mixedSqlNode, test);
      targetContents.add(ifSqlNode);
    }
  }


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

    @Override
    public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
      List<SqlNode> contents = parseDynamicTags(nodeToHandle);
      MixedSqlNode mixedSqlNode = new MixedSqlNode(contents);
      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);
    }
  }

6)MixedSqlNode会调用内部各个sqlNode的apply()方法

//ifSqlNode的apply方法
 public boolean apply(DynamicContext context) {
    if (evaluator.evaluateBoolean(test, context.getBindings())) {
      contents.apply(context);
      return true;
    }
    return false;
  }


//chooseSqlNode的apply方法
public boolean apply(DynamicContext context) {
    for (SqlNode sqlNode : ifSqlNodes) {
      if (sqlNode.apply(context)) {
        return true;
      }
    }
    if (defaultSqlNode != null) {
      defaultSqlNode.apply(context);
      return true;
    }
    return false;
  }



7)每个apply()方法的实现都会调用StaticTextSqlNode的apply()方法内部实现appendSql()方法

ForEachSqlNode标签验证#          检索替换字符串

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


   public void appendSql(String sql) {
      GenericTokenParser parser = new GenericTokenParser("#{", "}", new TokenHandler() {
        @Override
        public String handleToken(String content) {
          String newContent = content.replaceFirst("^\\s*" + item + "(?![^.,:\\s])", itemizeItem(item, index));
          if (itemIndex != null && newContent.equals(content)) {
            newContent = content.replaceFirst("^\\s*" + itemIndex + "(?![^.,:\\s])", itemizeItem(itemIndex, index));
          }
          return new StringBuilder("#{").append(newContent).append("}").toString();
        }
      });

      delegate.appendSql(parser.parse(sql));
    }

这样的SQL还只是个半成品,还需要org.apache.ibatis.scripting.xmltags.DynamicSqlSource的getBoundsql()方法

sqlSourceParser.parse()方法,处理方式跟前面处理"${}"占位符的基本一致。

8)使用DynamicSqlSource和RawSqlSource包装成一个完整的可执行BoundSql

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



 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 = parser.parse(originalSql);
    return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
  }

//替换为?占位符
 public String handleToken(String content) {
      parameterMappings.add(buildParameterMapping(content));
      return "?";
    }

好了,到这既介绍了复杂标签的解析,又详细的分析了从解析SQL到最后封装成可执行BoundSql

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值