Mybatis source code – implementation principle of dynamic SQL

Time:2022-5-11

preface

MybatisProvides powerful dynamicSQLStatement generation function to deal with complex business scenarios, this article will combineMybatisanalysisSQLStatement procedure pairMybatisMiddle pair<if><where><foreach>Equal dynamicSQLLabel support for analysis.

text

I Node concept in XML document

In analysisMybatisHow to supportSQLBefore the statement, this section analyzesXMLNode concept in the document.XMLEach component in the document is a node,DOMyesXMLThe node is specified as follows.

  • The entire document is aDocument node
  • eachXMLTag is aElement node
  • The text contained in the element node isText node

With oneXMLThe document is described as follows.

<provinces>
    < province name = "Sichuan" >
        < capital > Chengdu < / capital >
    </province>
    < province name = "Hubei" >
        < capital > Wuhan < / capital >
    </province>
</provinces>

As shown above, the wholeXMLA document is a document node. This document node has a child node, which is<provinces>Element node,<provinces>The element node has five child nodes: text node,<province>Element node, text node,<province>Element node and text node. Note that<provinces>The text values of the text nodes in the child nodes of the element node are\n, representing a newline character. Again,<province>The element node has three child nodes: text node,<capital>Element node and text node. The text value of the text node here is also\n, then<capital>The element node has only one child node, which is a text node. The child nodes of a node are brother nodes to each other, for example<provinces>The five child nodes of the element are brother nodes to each other,nameFor “Sichuan”<province>The previous sibling node of an element node is a text node, and the next sibling node is also a text node.

II Mybatis supports dynamic SQL source code analysis

It is known in mybatis source code – loading mapping file and dynamic agentXMLStatementBuilderofparseStatementNode()Method, the in the mapping file is parsed<select><insert><update>and<delete>Label (later uniformly referred to asCURDLabel) and generateMappedStatementThen cache toConfigurationYes.CURDLabel parsing is performed byXMLLanguageDriverAfter each tag is parsed, one will be generatedSqlSource, can be understood asSQLStatement, this section willXMLLanguageDriverHow to completeCURDLabel parsing is discussed.

XMLLanguageDriverestablishSqlSourceofcreateSqlSource()The method is as follows.

public SqlSource createSqlSource(Configuration configuration, 
        XNode script, Class<?> parameterType) {
    XMLScriptBuilder builder = new XMLScriptBuilder(
            configuration, script, parameterType);
    return builder.parseScriptNode();
}

As shown above,createSqlSource()Method,XNodenamelyCURDThe node corresponding to the label, increateSqlSource()Method first creates aXMLScriptBuilder, and then throughXMLScriptBuilderTo generateSqlSource。 Have a look firstXMLScriptBuilderThe construction method of is as follows.

public XMLScriptBuilder(Configuration configuration, XNode context, 
                    Class<?> parameterType) {
    super(configuration);
    this.context = context;
    this.parameterType = parameterType;
    initNodeHandlerMap();
}

stayXMLScriptBuilderIn the construction method ofCURDThe node corresponding to the tag is cached and initializednodeHandlerMapnodeHandlerMapStorage and handling inMybatisSupport dynamics providedSQLThe processor of the tag,initNodeHandlerMap()The method is as follows.

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());
}

Now analyzeXMLScriptBuilderofparseScriptNode()Method, which createsSqlSource, as shown below.

public SqlSource parseScriptNode() {
    //Parsing dynamic Tags
    MixedSqlNode rootSqlNode = parseDynamicTags(context);
    SqlSource sqlSource;
    if (isDynamic) {
        //Create dynamicsqlsource and return
        sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
    } else {
        //Create rawsqlsource and return
        sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
    }
    return sqlSource;
}

stayXMLScriptBuilderofparseScriptNode()In the method, according toXMLScriptBuilderMediumisDynamicAttribute judgment is to createDynamicSqlSourcestillRawSqlSource, it will not be analyzed here for the time beingDynamicSqlSourceAndRawSqlSourceBut it can be inferred thatparseDynamicTags()The method will changeisDynamicProperty, that is, inparseDynamicTags()The method will be based onCURDThe node of the label generates aMixedSqlNodeAt the same time, it will changeisDynamicProperty to indicate the currentCURDIn labelSQLWhether the statement is dynamic.MixedSqlNodeWhat is it?isDynamicWhen does the property value becometrue, with these questions, keep lookingparseDynamicTags()Method, as shown below.

protected MixedSqlNode parseDynamicTags(XNode node) {
    List<SqlNode> contents = new ArrayList<>();
    //Gets the child node of the node
    NodeList children = node.getNode().getChildNodes();
    //Traverse all child nodes
    for (int i = 0; i < children.getLength(); i++) {
        XNode child = node.newXNode(children.item(i));
        if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE 
                    || child.getNode().getNodeType() == Node.TEXT_NODE) {
            //The child node is a text node
            String data = child.getStringBody("");
            //Create a textsqlnode based on the value of the text node
            TextSqlNode textSqlNode = new TextSqlNode(data);
            //The isdynamic () method can determine whether the text node value has a ${} placeholder
            if (textSqlNode.isDynamic()) {
                //The text node value has a ${} placeholder
                //Add textsqlnode to the collection
                contents.add(textSqlNode);
                //Set isdynamic to true
                isDynamic = true;
            } else {
                //Text node value has no placeholder
                //Create a statictextsqlnode and add it to the collection
                contents.add(new StaticTextSqlNode(data));
            }
        } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) {
            //Child nodes are element nodes
            //The element nodes in the child nodes of curd node can only be dynamic SQL label nodes such as < if >, < foreach >
            String nodeName = child.getNode().getNodeName();
            //Obtain the corresponding processor according to the name of the dynamic SQL tag node
            NodeHandler handler = nodeHandlerMap.get(nodeName);
            if (handler == null) {
                throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
            }
            //Handle dynamic SQL tag nodes
            handler.handleNode(child, contents);
            //Set isdynamic to true
            isDynamic = true;
        }
    }
    //Create mixedsqlnode
    return new MixedSqlNode(contents);
}

Call according to the normal execution processparseDynamicTags()The input parameter isCURDLabel node, which will be traversedCURDAll child nodes of a label node. One is created based on each child nodeSqlNodeThen add toSqlNodeaggregatecontentsIn the endcontentsCreate as input parameterMixedSqlNodeAnd return.SqlNodeIs an interface, inparseDynamicTags()In the method, you can know,TextSqlNodeRealizedSqlNodeInterface,StaticTextSqlNodeRealizedSqlNodeInterface, so when the child node of a node is a text node, if the text value contains${}Placeholder, createTextSqlNodeAdd tocontentsAnd setisDynamicbytrue, if the text value does not contain${}Placeholder, createStaticTextSqlNodeAnd add tocontentsYes. IfCURDWhen the child node of a label node is an element node, becauseCURDThe element node of the label node can only be<if><foreach>Equal dynamicSQLLabel node, so it will be set directlyisDynamicbytrueAt the same time, dynamicSQLGenerated by the processor corresponding to the label nodeSqlNodeAnd add tocontentsYes. Here with<if>Name of the processor corresponding to the label nodehandleNode()The method is illustrated as an example, as shown below.

public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
    //Recursively call parsedynamictags() to parse < if > tag nodes
    MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
    String test = nodeToHandle.getStringAttribute("test");
    //Create ifsqlnode
    IfSqlNode ifSqlNode = new IfSqlNode(mixedSqlNode, test);
    //Add ifsqlnode to contents
    targetContents.add(ifSqlNode);
}

stay<if>Name of the processor corresponding to the label nodehandleNode()Method is called recursivelyparseDynamicTags()Method to analyze<if>Label node, for example<where><foreach>Of the processor corresponding to the tag nodehandleNode()Methods are also called recursivelyparseDynamicTags()This is because of these dynamicsSQLTags can be nested, such as<where>The child node of the label node can be<if>Label node. Through the abovehandleNode()Method, we can roughly knowMixedSqlNodeandIfSqlNodeAlso achievedSqlNodeInterface, let’s take a lookMixedSqlNodeandIfSqlNodeThe implementation of is as follows.

public class MixedSqlNode implements SqlNode {

    private final List<SqlNode> contents;

    public MixedSqlNode(List<SqlNode> contents) {
        this.contents = contents;
    }

    @Override
    public boolean apply(DynamicContext context) {
        contents.forEach(node -> node.apply(context));
        return true;
    }
    
}

public class IfSqlNode implements SqlNode {

    private final ExpressionEvaluator evaluator;
    private final String test;
    private final SqlNode contents;

    public IfSqlNode(SqlNode contents, String test) {
        this.test = test;
        this.contents = contents;
        this.evaluator = new ExpressionEvaluator();
    }

    @Override
    public boolean apply(DynamicContext context) {
        if (evaluator.evaluateBoolean(test, context.getBindings())) {
            contents.apply(context);
            return true;
        }
        return false;
    }

}

In fact, it has been gradually clear here. Call according to the normal execution processparseDynamicTags()Method is toCURDAll child nodes of a label node generate different labels according to the type of child nodesSqlNodeAnd put it onMixedSqlNodeIn, and thenMixedSqlNodeReturn, butCURDIf there is dynamic in the child nodes of the label nodeSQLLabel nodes because of these dynamicsSQLThe label node will also have child nodes, so it will be called recursively at this timeparseDynamicTags()Method to parse the dynamicSQLThe child nodes of the label node will also be generatedSqlNodeAnd put it onMixedSqlNodeAnd thenMixedSqlNodeReturn, recursive callparseDynamicTags()MethodMixedSqlNodeWill be saved in dynamicSQLCorresponding to label nodeSqlNodeMedium, for exampleIfSqlNodeWill be called recursivelyparseDynamicTags()GeneratedMixedSqlNodeAssign toIfSqlNodeofcontents

differentSqlNodeCan contain each other, this isCombined design patternApplication of,SqlNodeThe relationship between them is as follows.

Mybatis source code - implementation principle of dynamic SQL

SqlNodeInterface defines a method, as shown below.

public interface SqlNode {
      boolean apply(DynamicContext context);
}

eachSqlNodeofapply()Method, in addition to implementing its own logic, it also calls all its own logicSqlNodeofapply()Method, and finally call it layer by layer. AllSqlNodeofapply()Methods are executed.

Now backXMLScriptBuilderofparseScriptNode()Method, which will be calledparseDynamicTags()Method to parseCURDLabel nodes and getMixedSqlNodeMixedSqlNodeContains parsedCURDCorresponding to all child nodes of the label nodeSqlNodeFinally, based onMixedSqlNodeestablishDynamicSqlSourceperhapsRawSqlSource, ifCURDTags contain dynamicSQLLabel orSQLStatement contains${}Placeholder, createDynamicSqlSource, otherwise createRawSqlSource。 The following pairsDynamicSqlSourceandRawSqlSourceThe implementation of is analyzed.

DynamicSqlSourceThe implementation of is as follows.

public class DynamicSqlSource implements SqlSource {

    private final Configuration configuration;
    private final SqlNode rootSqlNode;

    public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) {
        //The constructor only performs a simple assignment operation
        this.configuration = configuration;
        this.rootSqlNode = rootSqlNode;
    }

    @Override
    public BoundSql getBoundSql(Object parameterObject) {
        DynamicContext context = new DynamicContext(configuration, parameterObject);
        //Call the apply() method of sqlnode to complete the generation of SQL statements
        rootSqlNode.apply(context);
        //Sqlsourcebuilder can replace the #{} placeholder in the SQL statement with?
        SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
        Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
        //Replace the #{} placeholder in the SQL statement with?, And generate a staticsqlsource
        SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
        //A dynamically generated SQL statement is saved in staticsqlsource, and #{} placeholders are all replaced with?
        BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
        //Generate an ordered parameter mapping list
        context.getBindings().forEach(boundSql::setAdditionalParameter);
        return boundSql;
    }

}

DynamicSqlSourceThe constructor of is just a simple assignment operation, and the focus is on itsgetBoundSql()Method, ingetBoundSql()Method, first callDynamicSqlSourceMediumSqlNodeofapply()Method to complete the dynamicSQLStatement is generated at this timeSQLThe placeholder (if any) in the statement is#{}, and then callSqlSourceBuilderofparse()Method willSQLPlaceholder in statement from#{}Replace with?And based on theSQLStatement generates aStaticSqlSourceAnd return, here you can have a lookStaticSqlSourceThe implementation of is as follows.

public class StaticSqlSource implements SqlSource {

    private final String sql;
    private final List<ParameterMapping> parameterMappings;
    private final Configuration configuration;

    public StaticSqlSource(Configuration configuration, String sql) {
        this(configuration, sql, null);
    }

    public StaticSqlSource(Configuration configuration, String sql, 
                           List<ParameterMapping> parameterMappings) {
        //Constructors are simply assignment operations
        this.sql = sql;
        this.parameterMappings = parameterMappings;
        this.configuration = configuration;
    }

    @Override
    public BoundSql getBoundSql(Object parameterObject) {
        //Create a boundsql based on the SQL statement and return
        return new BoundSql(configuration, sql, parameterMappings, parameterObject);
    }

}

So the analysis here, you can knowDynamicSqlSourceofgetBoundSql()Method actually completes dynamicSQLStatement generation and#{}Placeholder replacement, and then based on the generatedSQLStatement creationBoundSqlAnd return.BoundSqlThe class diagram of the object is shown below.

Mybatis source code - implementation principle of dynamic SQL

actually,MybatisMedium executionSQLStatement, ifSQLUsing dynamicSQLLabel, thenMybatisMediumExecutor(executor, which will be introduced in subsequent articles)MappedStatementofgetBoundSql()Method, and thenMappedStatementofgetBoundSql()Method will be called againDynamicSqlSourceofgetBoundSql()Method, soMybatisDynamics inSQLStatement will be generated when the statement is actually to be executed.

Now take a lookRawSqlSourceThe implementation of is as follows.

public class RawSqlSource implements SqlSource {

    private final SqlSource sqlSource;

    public RawSqlSource(Configuration configuration, SqlNode rootSqlNode, Class<?> parameterType) {
        //First call the getsql () method to get the SQL statement
        //Then execute the constructor
        this(configuration, getSql(configuration, rootSqlNode), parameterType);
    }

    public RawSqlSource(Configuration configuration, String sql, Class<?> parameterType) {
        SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
        Class<?> clazz = parameterType == null ? Object.class : parameterType;
        //Replace the #{} placeholder in the SQL statement with?, Generate a staticsqlsource and assign it to sqlsource
        sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap<>());
    }

    private static String getSql(Configuration configuration, SqlNode rootSqlNode) {
        DynamicContext context = new DynamicContext(configuration, null);
        rootSqlNode.apply(context);
        return context.getSql();
    }

    @Override
    public BoundSql getBoundSql(Object parameterObject) {
        //Actually, it calls the getboundsql () method of staticsqlsource
        return sqlSource.getBoundSql(parameterObject);
    }

}

RawSqlSourceWill be in the constructorSQLStatement generated and replaced#{}Placeholder, inSQLWhen the statement is actually to be executed, it will directly generate a goodSQLStatement returns. thereforeMybatisMedium, staticSQLStatements usually execute faster than dynamic statementsSQLStatement execution, which is inRawSqlSourceClass, as shown below.

Static SqlSource. It is faster than {@link DynamicSqlSource} because mappings are calculated during startup.

summary

MybatisFor each in the mapping fileCURDIn the tag nodeSQLStatement generates aSqlSource, if staticSQLStatement, theRawSqlSource, if dynamicSQLStatement, theDynamicSqlSourceMybatisIn generationSqlSourceWhen, will beCURDEach child node of the label node generates oneSqlNode, whether the child node is a text value node or a dynamic nodeSQLElement node, corresponding to all child nodesSqlNodeWill be placed inSqlSourceFor generationSQLStatement use. If staticSQLStatement, then createRawSqlSourceWill be used whenSqlNodecompleteSQLStatement generation andSQLIn a statement#{}Replace placeholder with?, and then save inRawSqlSourceWait until this staticSQLWhen the statement is to be executed, it returns the static message directlySQLsentence. If it’s dynamicSQLStatement, creatingDynamicSqlSourceWill simplySqlNodeSave it until this dynamicSQLStatement is used only when it is to be executedSqlNodecompleteSQLStatement generation andSQLIn a statement#{}Replace placeholder with?, and finally returnSQLStatement, soMybatisMedium, staticSQLStatements execute faster than dynamic statementsSQLsentence.

Recommended Today

Java custom form dynamic form designer workflow engine flowable design scheme

Workflow module——————————- 1.Model management: web online process designer, import / export XML, copy process, deployment process 2.*Process management*: import and export process resource files, view flow charts, reflect process models according to process instances, activate and suspend 3.Running process: view process information, current task node, current flowchart, void and suspend process, assign to-do personFree jump […]