玖叶教程网

前端编程开发入门

Mybatis动态标签详解之trim标签(mybatis的trim标签)

结论在最后,没有耐心或者觉得源码跟踪太乱的小伙伴直接看最后总结就可以了。

代码分析

代码舍弃了部分与本次没有太大关系的代码,没有阅读经验的小伙伴可能觉得有点乱,需要小伙伴们跟着源码一点一点的看才行。

我们直接从创建sqlSource开始,sqlSource里存放了原始sql,参数信息以及我们传的目标参数。

xml标签:

这里是我们的原始数据

select pp.PRODIST_PACKAGE_ID, pp.MAIN_ORDER_NUM, pp.PRODIST_OFFER_NUM, pp.OFFER_NUM, pp.OFFER_NAME,
        pp.PRODIST_PACKAGE_NUM, pp.POORD_PACKAGE_NUM, pp.PACKAGE_NAME, pp.PACKAGE_NUM, pp.HOST_COMPANY_NUM,
        pp.CUSTOMER_NUM, pp.CUSTOMER_NAME, pp.STATUS, pp.STATUS_DATE, pp.DESCRIPTION, pp.PACKAGE_BUSINESS_NUM,
        pp.PACKAGE_BUSINESS_NAME, pp.ORDER_SOURCE, pp.START_DATE, pp.END_DATE, pp.CREATE_STAFF_NUM, pp.CREATE_STAFF_NAME,
        pp.CREATE_DATE, pp.SUBSCRIBER_NUM, pp.OPERATION_SUB_TYPE, pp.OFFER_TYPE, pp.UPDATE_STAFF_NUM, pp.UPDATE_STAFF_NAME,
        pp.UPDATE_DATE, pp.SUBSCRIBER_NAME, pp.CONTRACT_MAIN_NAME, pp.CONTRACT_MAIN_NUM, pp.CUST_PROV_NUM,pp.CUST_TYPE, pp.CUST_SERV_LEVEL,
        pp.LOCATION,pp.CUST_MANAGER_STAFF_NUM, pp.ORDER_MODE_NAME, pp.ORDER_MODE_NUM, pp.VALIDATE_MODE_NAME, pp.VALIDATE_MODE_NUM from abs_archive.prodist_sku ps join abs_archive.prodist_package pp on ps.PRODIST_PACKAGE_NUM=pp.PRODIST_PACKAGE_NUM
        <trim prefix="where" prefixOverrides="AND | OR">
            <if test="@org.apache.commons.lang3.StringUtils@isNotBlank(list[0].prodistSkus[0].prodistPackageNum)">
                and pp.PRODIST_OFFER_NUM=#{list[0].prodistSkus[0].prodistPackageNum}
            </if>
</trim>

XMLStatementBuilder#parseStatementNode负责解析每一个statement标签。

这里是在parseStatementNode中创建sqlSource的方法,我们从这里开始看

XMLScriptBuilder#parseScriptNode负责解析每一个statement标签内部的sql以及动态标签。

//解析statement标签下的所有
public SqlSource parseScriptNode() {
        //这里会把所有的动态标签都解析,统一放到SqlNode中,包含了原始sql
  			//这里的context就是select标签了
        List<SqlNode> contents = parseDynamicTags(context);
 				//创建混合节点,组合模式中的根结点
        MixedSqlNode rootSqlNode = new MixedSqlNode(contents);
        SqlSource sqlSource = null;
        //${}
        if (isDynamic) {
            sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
        } else {
          	//这里会将MixedSqlNode存放到RawSqlSource中,我们后面看
            sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
        }
        return sqlSource;
    }
List<SqlNode> parseDynamicTags(XNode node) {
        List<SqlNode> contents = new ArrayList<SqlNode>();
  			//获取select标签下的所有的子标签
        NodeList children = node.getNode().getChildNodes();
        for (int i = 0; i < children.getLength(); i++) {
            XNode child = node.newXNode(children.item(i));
          	//判断是动态标签还是普通的sql,如果是字符串的话,则走这里
            if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
                String data = child.getStringBody("");
              	//先创建一个TextSqlNode,但是TextSqlNode是解析${}的,
              	//所以这里会创建一个StaticTextSqlNode
                TextSqlNode textSqlNode = new TextSqlNode(data);
          			//这里会解析当前的sql中是否有${},没有则创建StaticTextSqlNode,这个我们需要看一下
              	//会创建一个GenericTokenParser用于解析${}
              	//如果没有解析到${},则isDynamic=false,下面有代码
              if (textSqlNode.isDynamic()) {
                    contents.add(textSqlNode);
                    isDynamic = true;
                } else {
                  	//StaticTextSqlNode则是直接把字符串放了进去,没做任何操作,下面有代码
                    contents.add(new StaticTextSqlNode(data));
                }
            } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
              	//这里则是动态标签
                String nodeName = child.getNode().getNodeName();
              	//初始化所有的动态动态标签,然后获取到动态标签对应的那个NodeHandler,下面有代码
                NodeHandler handler = nodeHandlers(nodeName);
                if (handler == null) {
                    throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
                }
              	//会创建对应动态标签对应的SqlNode,下面有代码
              	//我们将重点研究trim标签
              	//将各个标签解析完创建成SqlNode并存放到contents中,然后返回
                handler.handleNode(child, contents);
                isDynamic = true;
            }
        }
        return contents;
    }

这里解释一下上面代码块中的几个关键代码:

  • 这里就是创建的GenericTokenParser解析器,这里放了${和},如果在我们的sql中使用了,则会将${}变换成?,同时将${}中的参数名存放到sqlSource的parameterMappings中
  • StaticTextSqlNode节点内部
  • 初始化所有的nodeHandlers
  • 我们这次研究的TrimHandler,里面创建了TrimSqlNode,一会我们仔细研究
  • MixedSqlNode方法:

我们来看一下当是trim标签时,TrimHandler是如何创建TrimSqlNode的:

我们可以看到当前nodeToHandle中的内容内部包含了一个IfSqlNode和两个StaticTextSqlNode,然后组成了一个MixedSqlNode,MixedSqlNode其实很简单,就是存入的SqlNode集合,然后循环调用apply方法

然后分别获取trim标签中的prefix、prefixOverrides、suffix、suffixOverrides

然后创建TrimSqlNode,同时将这几个参数传进去:

parseOverrides方法的作用是将prefixOverrides和suffixOverrides中的|去除,得到and和or或者其他字符串组成的集合

这里是创建完的TrimSqlNode,我们可以看到里面的内容

然后我们再回到上面的XMLScriptBuilder#parseScriptNode中

通过List<SqlNode> contents = parseDynamicTags(context);我们已经拿到了TrimSqlNode,

然后创建RawSqlSource,在RawSqlSource的构造方法中,我们可以看到getSql方法

在getSql中调用了MixedSqlNode的apply方法,我们知道MixedSqlNode的apply方法其实就是循环调用了内部的SqlNode集合,然后我们看一下TrimSqlNode的apply方法:


FilteredDynamicContext的目的是拼接sql字符串,它内部有一个sqlBuffer,其实就是个StringBuilder,然后经过contents.apply(filteredDynamicContext)后会获取trim标签内部的字符串:

接下来调用filteredDynamicContext.applyAll():

sqlBuffer中:and pp.PRODIST_OFFER_NUM=#{list[0].prodistSkus[0].prodistPackageNum}

applyPrefix方法:

private void applyPrefix(StringBuilder sql, String trimmedUppercaseSql) {
      if (!prefixApplied) {
        prefixApplied = true;
        //前缀:and、or
        if (prefixesToOverride != null) {
          //删除sql中前面的and或者or,这里注意的是只删除前面第一个的and或者or
          for (String toRemove : prefixesToOverride) {
            if (trimmedUppercaseSql.startsWith(toRemove)) {
              sql.delete(0, toRemove.trim().length());
              break;
            }
          }
        }
        //where
        if (prefix != null) {
          //最后在前面加上where标签
          sql.insert(0, " ");
          sql.insert(0, prefix);
        }
      }
    }

applySuffix方法:

private void applySuffix(StringBuilder sql, String trimmedUppercaseSql) {
      if (!suffixApplied) {
        suffixApplied = true;
        //后缀
        if (suffixesToOverride != null) {
          //,等
          for (String toRemove : suffixesToOverride) {
            if (trimmedUppercaseSql.endsWith(toRemove) || trimmedUppercaseSql.endsWith(toRemove.trim())) {
              int start = sql.length() - toRemove.trim().length();
              int end = sql.length();
              sql.delete(start, end);
              break;
            }
          }
        }
        if (suffix != null) {
          //在后面追加suffix
          sql.append(" ");
          sql.append(suffix);
        }
      }
    }

通过这两个方法,将prefix加到"and pp.PRODIST_OFFER_NUM=#{list[0].prodistSkus[0].prodistPackageNum}"之前,同时去除前面的prefixesToOverride;将"and pp.PRODIST_OFFER_NUM=#{list[0].prodistSkus[0].prodistPackageNum}"后面的suffixesToOverride去除掉,同时在后面追加suffix。

这里我们的prefix=where、prefixesToOverride=and|or,所以这里把"and pp.PRODIST_OFFER_NUM=#{list[0].prodistSkus[0].prodistPackageNum}"中的and去掉,然后加上where字符串。

总结

总结一下:trim标签一共有四个参数prefix、prefixOverrides、suffix、suffixOverrides

其中prefixOverrides、suffixOverrides支持|切割,我们可以使用or|and这种方式,最终会变成一个集合

首先会判断trim标签中sql是否以prefixOverrides中的某一个字符串开头,如果是,则去除sql中的这个字符串并终止循环

其次会判断prefix是否不为空,是则直接在sql前面加上一个空串,然后加上prefix

然后判断suffixOverrides是否有值,有的话,则判断sql是否以suffixOverrides中的某一个结尾,如果是,则去除该字符串,同时终止循环

最后,判断suffix不为空,则在sql最后面追加空串,然后加上suffix。

看到这里不知道小伙伴们有没有发现,我们可以通过trim实现where标签和set标签的功能

没错,where和set标签就是在trim标签的基础上进行实现的。

对于where标签,是设置了prefix=where,prefixOverrides=and|or的trim标签

对于set标签,是设置了prefix=set,suffixesToOverride=,的trim标签

发表评论:

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言