结论在最后,没有耐心或者觉得源码跟踪太乱的小伙伴直接看最后总结就可以了。
代码分析
代码舍弃了部分与本次没有太大关系的代码,没有阅读经验的小伙伴可能觉得有点乱,需要小伙伴们跟着源码一点一点的看才行。
我们直接从创建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标签