This is the 21st day of my participation in the August Text Challenge.More challenges in August

Sequence diagram

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)

The detailed steps

XMLLanguageDrive#createSqlSource

/** * The SqlSource object is generated primarily by the parseScriptNode method of XMLScriptBuilder *@paramConfiguration Indicates the configuration information *@paramDatabase operation node * in script mapping file@paramParameterType parameterType *@return* /
@Override
public SqlSource createSqlSource(Configuration configuration, XNode script, Class
        parameterType) {
    // Build the XMLScriptBuilder object and initialize the various Node processors
    XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
    / / parsing
    return builder.parseScriptNode();
}
Copy the code

XMLScriptBuilder

public XMLScriptBuilder(Configuration configuration, XNode context, Class
        parameterType) {
    super(configuration);
    this.context = context;
    this.parameterType = parameterType;
    // Initialize various Node processors
    initNodeHandlerMap();
}


/** * The mapping between SQL nodes and NodeHandler implementation classes is stored by nodeHandlerMap */
private void initNodeHandlerMap(a) {
    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());
}
Copy the code

XMLScriptBuilder#parseScriptNode

/** * Parse node to generate SqlSource object *@returnSqlSource object * /
public SqlSource parseScriptNode(a) {
    // Parse XML node to get node tree MixedSqlNode
    MixedSqlNode rootSqlNode = parseDynamicTags(context);
    SqlSource sqlSource;
    // Create a SqlSource object based on whether the node tree is dynamic
    if (isDynamic) {
        sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
    } else {
        sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
    }
    return sqlSource;
}
Copy the code

XMLScriptBuilder#parseDynamicTags

/** * parseDynamicTags parse the XNode object into a node tree * parseDynamicTags parses the nodes in the XML file level by level and processes the node using the corresponding NodeHandler implementation, * finally consolidating all the nodes into a MixedSqlNode object. MixedSqlNode object is the SQL node tree * * In the process of consolidating the node tree, as long as there is a dynamic node, the SQL node tree is dynamic. The dynamic SQL node tree is used to create the DynamicSqlSource object, otherwise the RawSqlSource object is created * *@paramNode The XNode object is the database operation node *@returnThe node tree */ is obtained after parsing
protected MixedSqlNode parseDynamicTags(XNode node) {
    // List of SQLNodes split from XNode
    List<SqlNode> contents = new ArrayList<>();
    // Enter a child XNode of XNode
    NodeList children = node.getNode().getChildNodes();
    for (int i = 0; i < children.getLength(); i++) {
        // Loop through each child XNode
        XNode child = node.newXNode(children.item(i));
        if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) { // XNode of type CDATA or text
            // Get the information in XNode
            String data = child.getStringBody("");
            TextSqlNode textSqlNode = new TextSqlNode(data);
            // MixedSqlNode is dynamic as long as one TextSqlNode object is dynamic
            if (textSqlNode.isDynamic()) {// Check whether the current node contains $, if so, it is dynamic
                contents.add(textSqlNode);
                isDynamic = true;
            } else {
                contents.add(newStaticTextSqlNode(data)); }}else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // Issue #628 // Child XNode types are still Node types
            String nodeName = child.getNode().getNodeName();
            // Find the corresponding dynamic label handler
            NodeHandler handler = nodeHandlerMap.get(nodeName);
            if (handler == null) {
                throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
            }
            // Use a dynamic label handler to process nodes
            handler.handleNode(child, contents);
            // If you include dynamic tags, the entire SQL is dynamic
            isDynamic = true; }}// Return a mixed node, which is essentially a tree of SQL nodes
    return new MixedSqlNode(contents);
}
Copy the code

TextSqlNode#isDynamic

TextSqlNode objects are dynamic if they contain "${}" placeholders, otherwise they are not. *@returnWhether the node is dynamic */
public boolean isDynamic(a) {
    // Placeholder handler, which does not process placeholders, but determines whether or not placeholders are present
    DynamicCheckerTokenParser checker = new DynamicCheckerTokenParser();
    GenericTokenParser parser = createParser(checker);
    // Use placeholder handlers. If the node content contains placeholders DynamicCheckerTokenParser isDynamic attribute of the object will be set to true
    parser.parse(text);
    return checker.isDynamic();
}


/** * create a generic placeholder parser to parse the ${} placeholder *@paramHandler The special handler * used to handle the ${} placeholder@returnPlaceholder parser */
private GenericTokenParser createParser(TokenHandler handler) {
    return new GenericTokenParser("${"."}", handler);
}
Copy the code

GenericTokenParser#parse

/** * This method does the job of locating the placeholder, and then hands off the replacement of the placeholder to its associated TokenHandler@param text
 * @return* /
public String parse(String text) {
    if (text == null || text.isEmpty()) {
        return "";
    }
    // search open token
    // Find the openToken location
    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;
    // The process continues only when openToken exists
    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);
            }
            // Concatenates characters from 0 to before openToken
            builder.append(src, offset, start - offset);
            // Set offset to the end of openToken
            offset = start + openToken.length();
            // Find the first closeToken position starting from the offset value
            int end = text.indexOf(closeToken, offset);
            // If yes, continue processing
            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();
                    // Continue to look for closeToken after the current closeToken
                    end = text.indexOf(closeToken, offset);
                } else {
                    expression.append(src, offset, end - offset);
                    break; }}// If it does not exist
            if (end == -1) {
                // close token was not found.
                // Concatenates the remaining characters
                builder.append(src, start, src.length - start);
                // Set offset to the length of the character array
                offset = src.length;
            } else {
                / * * * DynamicCheckerTokenParser: if there is, then set the current for dynamic SQL * /
                builder.append(handler.handleToken(expression.toString()));
                // Set offset to the end of closeToken
                offset = end + closeToken.length();
            }
        }
        start = text.indexOf(openToken, offset);
    }
    // Concatenates the remaining characters
    if (offset < src.length) {
        builder.append(src, offset, src.length - offset);
    }
    return builder.toString();
}
Copy the code

NodeHandler#handleNode

The implementation class Corresponding SqlNode implementation class
BindHandler VarDeclSqlNode
TrimHandler TrimSqlNode
WhereHandler WhereSqlNode
SetHandler SetSqlNode
ForEachHandler ForEachSqlNode
IfHandler IfSqlNode
OtherwiseHandler MixedSqlNode
ChooseHandler ChooseSqlNode
  • The following isWhereHandlerThe implementation in
@Override
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
    MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
    WhereSqlNode where = new WhereSqlNode(configuration, mixedSqlNode);
    targetContents.add(where);
}
Copy the code
  • WhereSqlNodeThe implementation of the codeTrimSqlNode, and is assigned by defaultprefix,prefixesToOverrideAttribute values
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); }}Copy the code

This is how Mybatis gets the SqlSource object.