Java training Mybatis dynamic Sql processing analysis

Time:2022-11-30

The following articles are from Architect Must-Have

Introduction to Dynamic Sql

Dynamic SQL is one of the powerful features of MyBatis. If you have used JDBC or other similar frameworks, you should understand how painful it is to concatenate SQL statements according to different conditions. For example, when concatenating, make sure not to forget to add necessary spaces, and also pay attention to remove the comma in the last column name of the list. Using dynamic SQL, you can completely get rid of this pain.

Working with dynamic SQL is not an easy task, but MyBatis significantly improves the ease of use of this feature with the powerful dynamic SQL language that can be used in any SQL mapped statement.

There are two core classes SqlNode, SqlSource, and ExpressionEvaluator in Mybatis dynamic analysis. The use of Mybatis dynamic Sql is divided into two parts: dynamic Sql parsing and dynamic Sql splicing execution.

Package SqlNode

SqlNode parses the dynamic Sql when parsing the Xml file, and stores it in the sqlSource attribute of MappedStatement. For nested dynamic Sql, mybatis uses recursive calls for parsing. Personally, I think this thing is still relatively convoluted, so this blogger prepares examples, source code, and execution results to explain _java training together.

Sql script classification

Sql scripts in Mybatis are divided into two types: static Sql and dynamic Sql. Let’s look at the difference between the two through the specific source code.

Static Sql and Dynamic Sql

Static Sql To put it bluntly, there is no Sql script that is too judgmental to understand.

// Select is some property of the query

<select id=”selectBypageTwo” resultType=”com.wwl.mybatis.dao.User”>

//This query statement select * from user where id > #{user.id} is the static Sql in Mybatis

//Static Sql is a Sql statement without any conditions

select * from user where id > #{ user.id}

//There are if judgment conditions here, and Mybatis calls Sql with judgment conditions dynamic Sql.

//Dynamic Sql also has foreach, where, trim, etc. in addition to if. Go to the mybatis official website for details.

<if test=”user.name != null and user.name!=””>

AND name = #{ user.name}

</if>

</select>

SqlNode class result system

Java training Mybatis dynamic Sql processing analysis

You can often see this structure when looking at the mybatis code. Each SqlNode is responsible for its own function. Single responsibility. The core method apply of SqlNode is to parse OGNL expression data through ExpressionEvaluator. Next, let’s see how Mybatis recursively parses dynamic sql scripts.

// Parse the Sql script node

public SqlSource parseScriptNode() {

//Parse static and dynamic scripts and store them in MixedSqlNode

//This line of code is very critical, we will analyze parseDynamicTags later, here is to recursively call this method layer by layer to generate a MixedSqlNode object from the Sql script.

MixedSqlNode rootSqlNode = parseDynamicTags(context);

SqlSource sqlSource = null;

//Whether it is dynamic Sql

if (isDynamic) {

//Dynamic Sql generates DynamicSqlSource

sqlSource = new DynamicSqlSource(configuration, rootSqlNode);

} else {

//Otherwise it is a static SqlSource

sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);

}

return sqlSource;

}

// An highlighted block

protected MixedSqlNode parseDynamicTags(XNode node) {

//Create a SqlNode, this list stores all the SqlNode information under the current Sql script node

List<SqlNode> contents = new ArrayList<SqlNode>();

NodeList children = node.getNode().getChildNodes();

for (int i = 0; i < children.getLength(); i++) {

XNode child = node.newXNode(children.item(i));

//Determine the text content in the sub-element or attribute || CDATA section in the sub-element document (text that will not be parsed by the parser)

if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {

String data = child.getStringBody(“”);

//parse data

TextSqlNode textSqlNode = new TextSqlNode(data);

//Judge whether the current Sql script is a dynamic script

if (textSqlNode.isDynamic()) {

contents.add(textSqlNode);

isDynamic = true;

} else {

contents.add(new StaticTextSqlNode(data));

}

//If the child element is a representative element, you need to parse the child element

} else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628

//Get the name of the element

String nodeName = child.getNode().getNodeName();

//Get the processor of the element node according to the element name, Mybatis provides 8 element processors, ChooseHandler, IfHandler, OtherwiseHandler

//TrimHandler, BindHandler, WhereHandler, SetHandler, ForEachHandler. The blogger will analyze IfHandler for you

NodeHandler handler = nodeHandlerMap.get(nodeName);

if (handler == null) {

throw new BuilderException(“Unknown element <” + nodeName + “> in SQL statement.”);

}

//Call the corresponding handler for node processing, the recursive call is here

handler.handleNode(child, contents);

isDynamic = true;

}

}

//Create MixedSqlNode

return new MixedSqlNode(contents);

}

// Let’s see how IfHandler handles it. IfHandler is an internal class of XMLScriptBuilder

private class IfHandler implements NodeHandler {

public IfHandler() {

// Prevent Synthetic Access

}

//We focus on analyzing this method

@Override

public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {

//Call parseDynamicTags for node parsing. Here is recursion, and the above method is called again.

MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);

// Get the expression corresponding to if

String test = nodeToHandle.getStringAttribute(“test”);

//Create IfSqlNode

IfSqlNode ifSqlNode = new IfSqlNode(mixedSqlNode, test);

targetContents.add(ifSqlNode);

}

}

Let’s analyze it based on the Sql script and execution results.

// Static Sql script and nested dynamic Sql script

<select id=”selectBypageTwo” resultType=”com.wwl.mybatis.dao.User”>

select * from user where id > #{ user.id}

<if test=”user.name != null and user.name!=””>

AND name = #{ user.name}

<if test=”user.name != null and user.name!=””>

AND name = #{ user.name}

<if test=”user.name != null and user.name!=””>

AND name = #{ user.name}

</if>

</if>

</if>

</select>

Let’s analyze the execution results:

Java training Mybatis dynamic Sql processing analysis

The above recursive results have been marked with unreasonable colors, you can see for yourself. In particular, you need to look at the properties of IfSqlNode.

Dynamic Sql analysis

Dynamic Sql parsing is mainly to convert dynamic Sql into Sql scripts that JDBC can recognize when performing database operations. In Mybatis, Sql scripts are mainly parsed through SqlSource and replaced with Sql scripts that JDBC can recognize. Let’s look at the class diagram first.

Java training Mybatis dynamic Sql processing analysis

SqlSource: Provides the behavior of Sql parsing.
RawSqlSource: Compilation of static Sql scripts, only generate StaticSqlSource once.
DynamicSqlSource: StaticSqlSource is generated for each call. The parameters passed in each call may be different. StaticSqlSource needs to be generated every time.
ProviderSqlSource: Integration of third-party scripting languages.
FreeMarkerSqlSource: Support for FreeMarker.
StaticSqlSource: StaticSqlSource only encapsulates the above 4 types. Bloggers will be more refreshed without this class.
This time we mainly analyze StaticSqlSource, RawSqlSource, and DynamicSqlSource.

StaticSqlSource

In fact, StaticSqlSource is to package the results of several other types of Sql processors. Let’s look at the source code.

//We mainly analyze getBoundSql

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) {

this.sql = sql;

this.parameterMappings = parameterMappings;

this.configuration = configuration;

}

//getBoundSql is to create a BoundSql object.

@Override

public BoundSql getBoundSql(Object parameterObject) {

return new BoundSql(configuration, sql, parameterMappings, parameterObject);

}

}

Is it very simple after reading it? In fact, some codes are not as difficult as we imagined.

RawSqlSource

// We focus on analyzing the RawSqlSource method

public class RawSqlSource implements SqlSource {

private final SqlSource sqlSource;

public RawSqlSource(Configuration configuration, SqlNode rootSqlNode, Class<?> parameterType) {

this(configuration, getSql(configuration, rootSqlNode), parameterType);

}

//The parsing of static scripts is implemented here. The so-called static script parsing is to parse #{} into? Static Sql parsing is performed when parsing Mapper.xml

public RawSqlSource(Configuration configuration, String sql, Class<?> parameterType) {

SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);

Class<?> clazz = parameterType == null ? Object.class : parameterType;

// Parse Sql by calling the parse method of SqlSourceBuilder

sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap<String, Object>());

}

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) {

return sqlSource.getBoundSql(parameterObject);

}

}

Let’s take a look at the parse method of SqlSourceBuilder

public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {

ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);

//Find the script for the #{} symbol in the Sql script? number to replace. The code in GenericTokenParser is relatively complicated, and the blogger has not studied it.

//If you are interested, you can study it yourself.

GenericTokenParser parser = new GenericTokenParser(“#{“, “}”, handler);

String sql = parser.parse(originalSql);

return new StaticSqlSource(configuration, sql, handler.getParameterMappings());

}

DynamicSqlSource

Dynamic Sql analysis is mainly done by DynamicSqlSource. Here again, sql analysis is performed through recursive calls. We still use the above Sql to explain to you.

public class DynamicSqlSource implements SqlSource {

private final Configuration configuration;

private final SqlNode rootSqlNode;

public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) {

this.configuration = configuration;

this.rootSqlNode = rootSqlNode;

}

@Override

public BoundSql getBoundSql(Object parameterObject) {

//Dynamic Sql parsing context

DynamicContext context = new DynamicContext(configuration, parameterObject);

//rootSqlNode is what we explained earlier, parsing dynamic Sql into SqlNode objects. The outer layer is the MixedSqlNode node, which stores

//All child nodes under the node. It calls recursively and checks whether splicing sql is required according to the attributes of the incoming parameters

rootSqlNode.apply(context);

//This code is consistent with the static Sql connection code above.

SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);

Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();

//Replace #{} in our dynamic Sql with?

SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());

BoundSql boundSql = sqlSource.getBoundSql(parameterObject);

for (Map.Entry<String, Object> entry : context.getBindings().entrySet()) {

boundSql.setAdditionalParameter(entry.getKey(), entry.getValue());

}

return boundSql;

}

}

The dynamic Sql analysis apply method blogger only introduces the apply method of MixedSqlNode and IfSqlNode according to the scene. Others are interested in doing their own research. The logic is roughly the same, but the implementation is somewhat different.

public class MixedSqlNode implements SqlNode {

private final List<SqlNode> contents;

public MixedSqlNode(List<SqlNode> contents) {

this.contents = contents;

}

//Get all SqlNodes in the loop SqlNode list, call the apply method to splice static sql according to the incoming parameters and conditions.

//The SqlNode in the list may be a simple SqlNode object, or it may be a MixedSqlNode or have more nesting.

//The blogger’s example is 3 nested If queries. According to the blogger’s Sql script, the apply method of IfSqlNode will be called directly here.

//Let’s see how IfSqlNode is implemented next.

@Override

public boolean apply(DynamicContext context) {

for (SqlNode sqlNode : contents) {

sqlNode.apply(context);

}

return true;

}

}

apply for IfSqlNode

public class IfSqlNode implements SqlNode {

//ExpressionEvaluator will call ognl to parse the expression

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) {

//context.getBindings() stores the request parameters, here is a HashMap, the code blogger in OGNl has not studied it.

//If the condition if is true, directly obtain the apply method of the SqlNode in the contents for dynamic script processing.

if (evaluator.evaluateBoolean(test, context.getBindings())) {

contents.apply(context);

return true;

}

return false;

}

}

This piece of code has a lot of recursive calls, and the blogger thinks that it is not very thorough, so you must debug it yourself after reading it.

Summarize

Mybatis dynamic Sql is divided into two processes from parsing to execution. The following is a brief summary of these two processes.
1. Dynamic Sql generates SqlNode information. This process occurs in the process of parsing Sql statements such as select and update. If it is static Sql, it will directly replace #{} with ? .
2. Dynamic Sql parsing is triggered when BoundSql is obtained. Will call the apply of SqlNode to parse Sql into static Sql, and then replace #{} with? , and bind the ParameterMapping mapping.

Recommended Today

Research on the realization of circular progress bar with linear gradient

This article mainly introduces the NutUI Vue3-basedcircleProgressThe design and implementation principle of the component is a circular progress bar component, which is used to display the current progress of the operation, and supports modification progress and gradient colors. The circular progress bar is a very commonly used component, especially on pages that manage background data […]