【Mybatis】Mybatis源码之获取SqlSource对象

799 阅读2分钟

这是我参与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实现类
BindHandlerVarDeclSqlNode
TrimHandlerTrimSqlNode
WhereHandlerWhereSqlNode
SetHandlerSetSqlNode
ForEachHandlerForEachSqlNode
IfHandlerIfSqlNode
OtherwiseHandlerMixedSqlNode
ChooseHandlerChooseSqlNode
  • 如下是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,并且默认赋值了prefixprefixesToOverride属性值
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对象的过程。