这是我参与8月更文挑战的第21天,活动详情查看:8月更文挑战
时序图
sequenceDiagram
participant A as XMLStatementBuilder
participant B as XMLLanguageDrive
participant C as XMLScriptBuilder
participant D as TextSqlNode
participant E as GenericTokenParser
A ->> B : createSqlSource
B ->> C : parseScriptNode
C ->> C : parseDynamicTags
C ->> D : isDynamic
D ->> E : parse
E -->> C : true/false
C -->> A : SqlSource(true-DynamicSqlSource/false-RawSqlSource)
详细步骤
XMLLanguageDrive#createSqlSource
/**
* SqlSource对象主要由XMLScriptBuilder的parseScriptNode方法生成
* @param configuration 配置信息
* @param script 映射文件中的数据库操作节点
* @param parameterType 参数类型
* @return
*/
@Override
public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
// 构建XMLScriptBuilder对象,并初始化各种node处理器
XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
// 解析
return builder.parseScriptNode();
}
XMLScriptBuilder
public XMLScriptBuilder(Configuration configuration, XNode context, Class<?> parameterType) {
super(configuration);
this.context = context;
this.parameterType = parameterType;
// 初始化各种node处理器
initNodeHandlerMap();
}
/**
* SQL节点和NodeHandler实现类的对应关系由nodeHandlerMap负责存储
*/
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());
}
XMLScriptBuilder#parseScriptNode
/**
* 解析节点生成SqlSource对象
* @return SqlSource对象
*/
public SqlSource parseScriptNode() {
// 解析XML节点,得到节点树MixedSqlNode
MixedSqlNode rootSqlNode = parseDynamicTags(context);
SqlSource sqlSource;
// 根据节点树是否为动态,创建对应的SqlSource对象
if (isDynamic) {
sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
} else {
sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
}
return sqlSource;
}
XMLScriptBuilder#parseDynamicTags
/**
* 将XNode对象解析为节点树
* parseDynamicTags 会逐级分析 XML 文件中的 节点并使用对应的NodeHandler 实现来处理该节点,
* 最终将所有的节点整合到一个 MixedSqlNode 对象中。MixedSqlNode对象就是 SQL节点树
*
* 在整合节点树的过程中,只要存在一个动态节点,则 SQL节点树就 是动态的。动态的SQL节点树将用来创建 DynamicSqlSource对象,否则 就创建 RawSqlSource对象
*
* @param node XNode对象,即数据库操作节点
* @return 解析后得到的节点树
*/
protected MixedSqlNode parseDynamicTags(XNode node) {
// XNode拆分出的SqlNode列表
List<SqlNode> contents = new ArrayList<>();
// 输入XNode的子XNode
NodeList children = node.getNode().getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
// 循环遍历每一个子XNode
XNode child = node.newXNode(children.item(i));
if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) { // CDATA类型或者text类型的XNode节点
// 获取XNode内的信息
String data = child.getStringBody("");
TextSqlNode textSqlNode = new TextSqlNode(data);
// 只要有一个TextSqlNode对象是动态的,则整个MixedSqlNode就是动态的
if (textSqlNode.isDynamic()) {// 判断当前节点是否包含$,如果包含,则是动态的
contents.add(textSqlNode);
isDynamic = true;
} else {
contents.add(new StaticTextSqlNode(data));
}
} else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628 // 子XNode类型任然是Node类型
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);
// 如果包含动态标签,则整个SQL就是动态的
isDynamic = true;
}
}
// 返回一个混合节点,其实就是一个SQL节点树
return new MixedSqlNode(contents);
}
TextSqlNode#isDynamic
/**
* 判断当前节点是不是动态的
* 对于 TextSqlNode对象而言,如果内 部含有“${}”占位符,那它就是动态的,否则就不是动态的。
* @return 节点是否为动态
*/
public boolean isDynamic() {
// 占位符处理器,该处理器并不会处理占位符,而是判断是不是含有占位符
DynamicCheckerTokenParser checker = new DynamicCheckerTokenParser();
GenericTokenParser parser = createParser(checker);
// 使用占位符处理器。如果节点内容中含有占位符,则DynamicCheckerTokenParser对象的isDynamic属性将会被置为true
parser.parse(text);
return checker.isDynamic();
}
/**
* 创建一个通用的占位符解析器,用来解析${}占位符
* @param handler 用来处理${}占位符的专用处理器
* @return 占位符解析器
*/
private GenericTokenParser createParser(TokenHandler handler) {
return new GenericTokenParser("${", "}", handler);
}
GenericTokenParser#parse
/**
* 该方法主要 完成占位符的定位工作,然后把占位符的替换工作交给与其关联的 TokenHandler 处理
* @param text
* @return
*/
public String parse(String text) {
if (text == null || text.isEmpty()) {
return "";
}
// search open token
// 查找openToken的位置
int start = text.indexOf(openToken);
if (start == -1) {
return text;
}
char[] src = text.toCharArray();
int offset = 0;
final StringBuilder builder = new StringBuilder();
StringBuilder expression = null;
// 当存在openToken时,才继续处理
while (start > -1) {
if (start > 0 && src[start - 1] == '\\') {
// this open token is escaped. remove the backslash and continue.
builder.append(src, offset, start - offset - 1).append(openToken);
offset = start + openToken.length();
} else {
// found open token. let's search close token.
if (expression == null) {
expression = new StringBuilder();
} else {
expression.setLength(0);
}
// 拼接从0到openToken之前的字符
builder.append(src, offset, start - offset);
// 设置offset值为openToken结束的位置
offset = start + openToken.length();
// 从offset值之后开始找第一个closeToken的位置
int end = text.indexOf(closeToken, offset);
// 如果存在,则继续处理
while (end > -1) {
if (end > offset && src[end - 1] == '\\') {
// this close token is escaped. remove the backslash and continue.
expression.append(src, offset, end - offset - 1).append(closeToken);
offset = end + closeToken.length();
// 继续查找当前closeToken之后的closeToken
end = text.indexOf(closeToken, offset);
} else {
expression.append(src, offset, end - offset);
break;
}
}
// 如果不存在
if (end == -1) {
// close token was not found.
// 拼接剩余的字符
builder.append(src, start, src.length - start);
// 设置offset为字符数组的长度
offset = src.length;
} else {
/**
* DynamicCheckerTokenParser:如果存在,则设置当前SQL为动态的
*/
builder.append(handler.handleToken(expression.toString()));
// 设置offset值为closeToken结束的位置
offset = end + closeToken.length();
}
}
start = text.indexOf(openToken, offset);
}
// 拼接剩余的字符
if (offset < src.length) {
builder.append(src, offset, src.length - offset);
}
return builder.toString();
}
NodeHandler#handleNode
| 实现类 | 对应的SqlNode实现类 |
|---|---|
| BindHandler | VarDeclSqlNode |
| TrimHandler | TrimSqlNode |
| WhereHandler | WhereSqlNode |
| SetHandler | SetSqlNode |
| ForEachHandler | ForEachSqlNode |
| IfHandler | IfSqlNode |
| OtherwiseHandler | MixedSqlNode |
| ChooseHandler | ChooseSqlNode |
- 如下是
WhereHandler中的实现
@Override
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
WhereSqlNode where = new WhereSqlNode(configuration, mixedSqlNode);
targetContents.add(where);
}
WhereSqlNode的代码,实现了TrimSqlNode,并且默认赋值了prefix、prefixesToOverride属性值
public class WhereSqlNode extends TrimSqlNode {
private static List<String> prefixList = Arrays.asList("AND ", "OR ", "AND\n", "OR\n", "AND\r", "OR\r", "AND\t", "OR\t");
public WhereSqlNode(Configuration configuration, SqlNode contents) {
super(configuration, contents, "WHERE", prefixList, null, null);
}
}
以上便是Mybatis获取SqlSource对象的过程。