I found that it is really difficult to insist on writing a blog, and various reasons will lead to no need to take care of the blog. I originally planned to write a DIY implementation of orm and see the time. I would rather implement a dynamic SQL first and add the complete implementation of orm next time I have time.
People who have used mybatis are probably familiar with dynamic SQL. If you haven't used it, just take a look at the fun. The first time I came into contact with mysql was when I was in my senior year. At that time, I thought dynamic sql was very awesome and flexible. I always wanted to figure out how to implement it. Although I could write Ioc, mvc and simple orm frameworks at that time (imitate mybaits but there is no dynamic sql part), I still couldn't find where to implement the dynamic sql in the core of mybatis and how to implement it. Maybe the code was too tangled and I couldn't understand it at all. Until now, I didn't have the courage to read the dynamic sql part of mybatis. Maybe I was born with inexplicable awe of algorithms.
A few years ago, because I wanted to build a configuration platform and wanted to use a parsing language to replace Java implementation, which allowed configuration personnel to easily write a small amount of code on the page to implement complex business logic (including database operations). At that time, Java already had a js parsing engine, but most people said that the efficiency was too low. If I didn’t know what I was crazy about, I thought of implementing a parsing language myself. However, I have always dreamed of realizing my own language. It is easier to start with analytical languages than compiled languages, so I started to do it decisively. After writing it, I realized that my implementation is probably not as efficient as the js engine at that time. At that time, I was really young and simple. The dynamic SQL implementation I am talking about today is actually inspired by the parsing language at that time.
Let’s talk about dynamic SQL without saying much nonsense. Please see the following example. First, I declare that the example here is not a correct way to write SQL. I just want to write a nested structure that is as complex as possible. If this complex situation is implemented, then it is even more difficult to make it simple.
delete from pl_pagewidget<if test="widgetcodes != null"> where pagewidgetcode in <foreach collection="widgetcodes" item="item" index="index" open="(" separator="," close=")"> <if test="index == 0"> #{item} </if> <foreach collection="bs" item="b" index="index1" open="(" separator="," close=")"> #{b} </foreach> </foreach></if><if test="a != null"> and a = #{a}</if>To implement SQL for parsing the above example, one of the difficulties is similar to how to determine the true or false conditions in the test attribute. However, this difficulty is more in front of the ognl expression learned in struts2. I don't know if a friend has encountered a rather weird phenomenon, that is, sometimes the following expression is written in mybatis dynamic SQL, but when n=0, it actually meets the condition, that is, the value in test is false, and 0 cannot meet the conditions of this expression. This is the reason for the ognl library. There is no way it just plays like this, just remember it as a special situation
test="n != null and n !=''"
The ognl expression is very convenient to use as follows
import java.util.HashMap;import java.util.Map;import ognl.Ognl;public class OgnlTest { //Output result: false public static void main(String[] args) throws Exception { String con1 = "n != null and n != ''"; Map<String,Object> root = new HashMap<>(); root.put("n", 0); System.out.println(Ognl.getValue(con1,root)); }}To implement SQL for parsing the above example, the second difficulty is that although this SQL is covered with a layer of XML, it is a standard SQL, as follows
<sql> delete from pl_pagewidget <if test="widgetcodes != null"> where pagewidgetcode in <foreach collection="widgetcodes" item="item" index="index" open="(" separator="," close=")"> <if test="index == 0"> #{item} </if> <foreach collection="bs" item="b" index="index1" open="(" separator="," close=")"> #{b} </foreach> </foreach> </if> <if test="a != null"> and a = #{a} </if></sql>However, parsing the above xml is different from our usual ones. This xml is a mixture of tags and text. Normally, we should rarely use parsing this xml in development. However, the commonly used tool for parsing XML dom4j can actually parse this kind of SQL very well, but it is rarely used. The content() method of the Element class can return a collection of Nodes, and then traverse this collection and judge the type of each Node. After solving these two key points, you only need to add a little trick to parse this dynamic SQL.
The trick I used was inspired by the Java syntax format. For example, there are local variables and global variables in Java, and the situation of reference passing is not considered. If the global variable int i = 1; the global variable is passed into the method and then modified in the method. What you see in the method is the changed value, but what you see outside the method is still 1. In fact, you should know this phenomenon after learning Java. Also, when the method is called, you can see global variables and local variables in the method. After the method call is finished, the local variables will be cleared and released (please be happy to see the garbage collector). I have introduced these and directly added the code
import java.io.StringReader;import java.text.SimpleDateFormat;import java.util.Arrays;import java.util.Date;import java.util.HashMap;import java.util.List;import java.util.Map;import java.util.regex.Matcher;import java.util.regex.Pattern;import org.apache.commons.collections.MapUtils;import org.apache.commons.lang.StringUtils;import org.dom4j.Document;import org.dom4j.Element;import org.dom4j.Node;import org.dom4j.Text;import org.dom4j.io.SAXReader;import com.rd.sql.Attrs;import com.rd.sql.BaseNode;import com.rd.sql.NodeFactory;public class SqlParser { private Map<String,Object> currParams = new HashMap<String,Object>(); /** delete from pl_pagewidget <if test="widgetcodes != null"> where pagewidgetcode in <foreach collection="widgetcodes" item="item" index="index" open="(" separator="," close=")"> <if test="index == 0"> #{item} </if> <foreach collection="bs" item="b" index="index1" open="(" separator="," close=")"> #{b} </foreach> </foreach> </if> <if test="a != null"> and a = #{a} </if> */ public static void main(String[] args) throws Exception { Map<String, Object> map = new HashMap<String, Object>(); map.put("widgetcodes", Arrays.asList("1", "2")); map.put("bs", Arrays.asList("3", "4")); map.put("a", 1); SqlParser parser = new SqlParser(); System.out .println(parser.parser("delete from pl_pagewidget/n" + "/t<if test=/"widgetcodes != null/">/n" + "/t/twhere pagewidgetcode in/n" + "/t/t<foreach collection=/"widgetcodes/" item=/"item/" index=/"index/" open=/"(/" separator=/",/" close=/")/">/n" + "/t/t <if test=/"index == 0/">/n" + "/t/t #{item}/n" + "/t/t </if>/n" + "/t/t <foreach collection=/"bs/" item=/"b/" index=/"index1/" open=/"(/" separator=/",/" close=/")/">/n" + "/t/t/t#{b}/n" + "/t/t </foreach>/n" + "/t/t</foreach>/n" + "/t</if>/n" + "/t<if test=/"a != null/">/n" + "/t/t a = #{a}/n" + "/t</if>/n", map)); System.out.println(parser.getParams()); } public String parser(String xml, Map<String, Object> params) throws Exception { // xml = "<?xml version=/"1.0/" encoding=/"UTF-8/"?>"+xml; //Set a layer of xml tags for the input dynamic sql xml = "<sql>"+xml+"</sql>"; SAXReader reader = new SAXReader(false); Document document = reader.read(new StringReader(xml)); Element element = document.getRootElement(); Map<String, Object> currParams = new HashMap<String, Object>(); StringBuilder sb = new StringBuilder(); //Start parserElement(element, currParams, params, sb); return sb.toString(); } /** * Use recursive parser to parse dynamic sql * @param ele1 xml tag to be parsed* @param currParams * @param globalParams * @param sb * @throws Exception */ private void parserElement(Element ele1, Map<String, Object> currParams, Map<String, Object> globalParams, StringBuilder sb) throws Exception { // Parse a node, for example, parse an if node, if test determines true, it returns true. TempVal val = parserOneElement(currParams, globalParams, ele1, sb); //The abstract node object of the node parsed BaseNode node = val.getNode(); /** * In fact, the statement above this sentence only parses the xml tag and does not parse the content in the tag. Here * means that before parsing the content, if there is a pre-operation, do some pre-operation*/ node.pre(currParams, globalParams, ele1, sb); //Defend whether the content in the node still needs to be parsed, for example, if the test result is true boolean flag = val.isContinue(); // Get a collection of all child nodes under this node, including the normal text List<Node> nodes = ele1.content(); if (flag && !nodes.isEmpty()) { /** * This means that you want to further parse the content in the node. You can compare the node into the shell of a method* The content in the inside is analogous to the specific statements in the method. Before starting to parse the content of the node* Create a container with local parameters under this node. The most convenient is of course map */ Map<String, Object> params = new HashMap<String, Object>(); /** * Put the local parameters passed in outside directly into the container, because the parameters in this example are common data types* There will be no reference type, so this is equivalent to a copy. In order not to affect the object passed in outside*, you can compare the case where the method calls the incoming parameters*/ params.putAll(currParams); //Loop all child nodes for (int i = 0; i < nodes.size();) { Node n = nodes.get(i); //If the node is a normal text if (n instanceof Text) { String text = ((Text) n).getStringValue(); if (StringUtils.isNotEmpty(text.trim())) { //Train the text, such as processing #{xx}, directly replace ${yy} with the real value passed in sb.append(handText(text, params,globalParams)); } i++; } else if (n instanceof Element) { Element e1 = (Element) n; // Recursively parse xml child elements parserElement(e1, params, globalParams, sb); // If the loop flag is not true, parse the next tag// This means that you need to repeatedly parse the loop tag, then i will not change, otherwise continue to process the next element boolean while_flag = MapUtils.getBoolean(params, Attrs.WHILE_FLAG, false); if (!while_flag || !NodeFactory.isWhile(n.getName()) || e1.attributeValue(Attrs.INDEX) == null || !e1.attributeValue(Attrs.INDEX).equals( params.get(Attrs.WHILE_INDEX))) { i++; } } } //What should I do after node processing node.after(currParams, globalParams, ele1, sb); // Recycle the current scope parameter params.clear(); params = null; } } /** * Process text to replace the parameter #{item}* @param str * @param params * @return * @throws Exception */ private String handText(String str, Map<String, Object> params,Map<String, Object> globalParams) throws Exception { //Get the variable String indexStr = MapUtils.getString(params, Attrs.WHILE_INDEX); Integer index = null; if(StringUtils.isNotEmpty(indexStr)) { index = MapUtils.getInteger(params, indexStr); } //Match #{a} parameter String reg1 = "(#//{)(//w+)(//})"; //Match the parameter of ${a}String reg2 = "(//$//{)(//w+)(//})"; Pattern p1 = Pattern.compile(reg1); Matcher m1 = p1.matcher(str); Pattern p2 = Pattern.compile(reg2); Matcher m2 = p2.matcher(str); String whileList = MapUtils.getString(params, Attrs.WHILE_LIST); Map<String,Object> allParams = getAllParams(params, globalParams); while(m1.find()) { String tmpKey = m1.group(2); String key = whileList == null?tmpKey:(whileList+"_"+tmpKey); key = index == null?key:(key+index); String reKey = "#{"+key+"}"; //If in a loop similar to foreach, you may need to replace the parameter #{xx} with #{xx_0},#{xx_1} str = str.replace(m1.group(0), reKey); currParams.put(key, allParams.get(tmpKey)); } while(m2.find()) { String tmpKey = m2.group(2); Object value = allParams.get(tmpKey); if(value != null) { str = str.replace(m2.group(0), getValue(value)); } } return str; } private String getValue(Object value) { String result = ""; if(value instanceof Date) { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); result = sdf.format((Date)value); } else { result = String.valueOf(value); } return result; } private Map<String, Object> getAllParams(Map<String, Object> currParams, Map<String, Object> globalParams) { Map<String,Object> allParams = new HashMap<String,Object>(); allParams.putAll(globalParams); allParams.putAll(currParams); return allParams; } // parse an xml element private TempVal parserOneElement(Map<String, Object> currParams, Map<String, Object> globalParams, Element ele, StringBuilder sb) throws Exception { //Get the xml tag name String eleName = ele.getName(); //Does it continue after parsing a node? If you encounter a node like if, you need to determine whether the test is empty. boolean isContinue = false; //Declare an abstract node BaseNode node = null; if (StringUtils.isNotEmpty(eleName)) { //Use the node factory to get a node object based on the node name, such as if node or foreach node node = NodeFactory.create(eleName); //Analyze this node and return whether the content in the node still needs to be parsed isContinue = node.parse(currParams, globalParams, ele, sb); } return new TempVal(isContinue, ele, node); } public Map<String, Object> getParams() { return currParams; } /** * Encapsulate the result after an xml element is parsed* @author rongdi */ final static class TempVal { private boolean isContinue; private Element ele; private BaseNode node; public TempVal(boolean isContinue, Element ele, BaseNode node) { this.isContinue = isContinue; this.ele = ele; this.node = node; } public boolean isContinue() { return isContinue; } public void setContinue(boolean isContinue) { this.isContinue = isContinue; } public Element getEle() { return ele; } public void setEle(Element ele) { this.ele = ele; } public BaseNode getNode() { return node; } public void setNode(BaseNode node) { this.node = node; } }} import org.dom4j.Element;import java.util.HashMap;import java.util.Map;/** * Abstract node* @author rongdi */public abstract class BaseNode { public abstract boolean parse(Map<String, Object> currParams, Map<String, Object> globalParams, Element ele,StringBuilder sb) throws Exception; public void pre(Map<String, Object> currParams,Map<String, Object> globalParams,Element ele,StringBuilder sb) throws Exception { } public void after(Map<String, Object> currParams,Map<String, Object> globalParams,Element ele,StringBuilder sb) throws Exception { } protected Map<String, Object> getAllParams(Map<String, Object> currParams, Map<String, Object> globalParams) { Map<String,Object> allParams = new HashMap<String,Object>(); allParams.putAll(globalParams); allParams.putAll(currParams); return allParams; }} import java.util.Map;import ognl.Ognl;import org.apache.commons.lang.StringUtils;import org.dom4j.Element;/** * if node* @author rongdi */public class IfNode extends BaseNode{ @Override public boolean parse(Map<String, Object> currParams, Map<String, Object> globalParams, Element ele,StringBuilder sb) throws Exception { //Get the test attribute of if node String testStr = ele.attributeValue("test"); boolean test = false; try { if(StringUtils.isNotEmpty(testStr)) { //Merge global variables and local variables Map<String, Object> allParams = getAllParams(currParams,globalParams); //Use ognl to determine true or false test = (Boolean) Ognl.getValue(testStr,allParams); } } catch (Exception e) { e.printStackTrace(); throw new Exception("Judge operation parameters"+testStr+"Illegal"); } if(ele.content() != null && ele.content().size()==0) { test = true; } return test; }} import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Map;import java.util.Set;import ognl.Ognl;import org.apache.commons.collections.MapUtils;import org.apache.commons.lang.StringUtils;import org.dom4j.Element;/** The attributes of the foreach node are as follows collection item that needs to be traversed. The variable index stored in each element after traversing the collection. The index number of the collection is such as 0, 1, 2... separator After traversing, splicing open with a specified separator. The symbols that start the splicing after traversal are as follows (close The symbols that end the splicing after traversal are as follows) */public class ForeachNode extends BaseNode { @Override public boolean parse(Map<String, Object> currParams, Map<String, Object> globalParams, Element ele, StringBuilder sb) throws Exception { String conditionStr = null; String collectionStr = ele.attributeValue("collection"); String itemStr = ele.attributeValue("item"); String index = ele.attributeValue("index"); String separatorStr = ele.attributeValue("separator"); String openStr = ele.attributeValue("open"); String closeStr = ele.attributeValue("close"); if(StringUtils.isEmpty(index)) { index = "index"; } if(StringUtils.isEmpty(separatorStr)) { separatorStr = ","; } if(StringUtils.isNotEmpty(openStr)) { currParams.put(Attrs.WHILE_OPEN,openStr); } if(StringUtils.isNotEmpty(closeStr)) { currParams.put(Attrs.WHILE_CLOSE,closeStr); } if(StringUtils.isNotEmpty(collectionStr)) { currParams.put(Attrs.WHILE_LIST,collectionStr); } currParams.put(Attrs.WHILE_SEPARATOR,separatorStr); if(index != null) { /** * If there is the value of the current loop variable in the local variable, it means that it is not the first time that you have entered the loop label. Remove the start tag* and add 1 to the local variable value */ if(currParams.get(index) != null) { currParams.remove(Attrs.WHILE_START); currParams.put(index+"_", (Integer)currParams.get(index+"_") + 1); } else { //The first time you enter the loop label currParams.put(Attrs.WHILE_START,true); currParams.put(index+"_", 0); } currParams.put(index, (Integer)currParams.get(index+"_")); } boolean condition = true; Map<String, Object> allParams = getAllParams(currParams,globalParams); Object collection = null; if(StringUtils.isNotEmpty(collectionStr)) { //Get the collection to be looped collection = Ognl.getValue(collectionStr,allParams); //If the collection property is not empty, but the condition is null, a boundary condition is added by default if(StringUtils.isEmpty(conditionStr)) { //Here I'll just use a collection to demonstrate it. You can also add an array, but just change it to .length if(collection instanceof List) { conditionStr = index+"_<"+collectionStr+".size()"; } else if(collection instanceof Map){ Map map = (Map)collection; Set set = map.entrySet(); List list = new ArrayList(set); allParams.put("_list_", list); conditionStr = index+"_<_list_"+".size()"; } } } currParams.remove(Attrs.WHILE_END); if(StringUtils.isNotEmpty(conditionStr)) { //The value of the calculation condition condition = (Boolean)Ognl.getValue(conditionStr,allParams); Map<String,Object> tempMap = new HashMap<>(); tempMap.putAll(allParams); tempMap.put(index+"_",(Integer)currParams.get(index+"_") + 1); currParams.put(Attrs.WHILE_END,!(Boolean)Ognl.getValue(conditionStr,tempMap)); } boolean flag = true; currParams.put(Attrs.WHILE_INDEX, index); currParams.put(Attrs.WHILE_FLAG, true); if(condition) { try { if(StringUtils.isNotEmpty(itemStr) && StringUtils.isNotEmpty(collectionStr)) { Object value = null; int idx = Integer.parseInt(currParams.get(index+"_").toString()); if(collection instanceof List) { value = ((List)collection).get(idx); currParams.put(itemStr, value); } else if(collection instanceof Map){ Map map = (Map)collection; Set<Map.Entry<String,Object>> set = map.entrySet(); List<Map.Entry<String,Object>> list = new ArrayList(set); currParams.put(itemStr, list.get(idx).getValue()); currParams.put(index, list.get(idx).getKey()); } } } catch (Exception e) { throw new Exception("Get value from a collection or map"+currParams.get(index)+"Error"+e.getMessage()); } } else { flag = false; destroyVars(currParams, index, itemStr); } return flag; } /** * If it is the first time you enter the loop tag, spell the content of open*/ @Override public void pre(Map<String, Object> currParams, Map<String, Object> globalParams, Element ele, StringBuilder sb) throws Exception { super.pre(currParams, globalParams, ele, sb); boolean start = MapUtils.getBoolean(currParams,Attrs.WHILE_START,false); if(start) { String open = MapUtils.getString(currParams,Attrs.WHILE_OPEN); sb.append(open); } } /** * If the loop label is finally entered, the content of close is spelled in the end*/ @Override public void after(Map<String, Object> currParams, Map<String, Object> globalParams, Element ele, StringBuilder sb) throws Exception { super.after(currParams, globalParams, ele, sb); boolean end = MapUtils.getBoolean(currParams,Attrs.WHILE_END,false); String separator = MapUtils.getString(currParams,Attrs.WHILE_SEPARATOR); if(!end && StringUtils.isNotEmpty(separator)) { sb.append(separator); } if(end) { String close = MapUtils.getString(currParams,Attrs.WHILE_CLOSE); if(sb.toString().endsWith(separator)) { sb.deleteCharAt(sb.length() - 1); } sb.append(close); } } //Release the temporary variable private void destroyVars(Map<String, Object> currParams, String index,String varStr) { currParams.remove(Attrs.WHILE_INDEX); currParams.remove(Attrs.WHILE_FLAG); currParams.remove(Attrs.WHILE_SEPARATOR); currParams.remove(Attrs.WHILE_START); currParams.remove(Attrs.WHILE_END); currParams.remove(Attrs.WHILE_LIST); }} import org.dom4j.Element;import java.util.Map;public class SqlNode extends BaseNode{ @Override public boolean parse(Map<String, Object> currParams, Map<String, Object> globalParams, Element ele,StringBuilder sb) throws Exception { return true; }}import java.util.Arrays;import java.util.List;import java.util.Map;import java.util.concurrent.ConcurrentHashMap;/** * Node Factory*/public class NodeFactory { private static Map<String,BaseNode> nodeMap = new ConcurrentHashMap<String,BaseNode>(); private final static List<String> whileList = Arrays.asList("foreach"); static { nodeMap.put("if", new IfNode()); nodeMap.put("sql", new SqlNode()); nodeMap.put("foreach", new ForeachNode()); } public static boolean isWhile(String elementName) { return whileList.contains(elementName); } public static void addNode(String nodeName,BaseNode node) { nodeMap.put(nodeName, node); } public static BaseNode create(String nodeName) { return nodeMap.get(nodeName); } }/** * Various tags* @author rongdi */public class Attrs { public final static String TRANSACTIONAL = "transactional"; public final static String WHILE_START = "while-start"; public final static String WHILE_END = "while-end"; public final static String WHILE_OPEN = "while-open"; public final static String WHILE_CLOSE = "while-close"; public final static String WHILE_SEPARATOR = "while-separator"; public final static String WHILE_INDEX = "while-index"; public final static String WHILE_FLAG = "while-flag"; public final static String WHILE_LIST = "while-list"; public final static String WHEN_FLAG = "when-flag"; public static final String PROCESS_VAR = "process-var"; public final static String RESULT_FLAG = "result-flag"; public final static String RETURN_FLAG = "return-flag"; public final static String CONSOLE_VAR= "console-var"; public final static String DO = "do"; public final static String INDEX = "index"; public final static String CONDITION = "condition"; public final static String NAME= "name"; public final static String VALUE= "value"; public static final String TYPE = "type"; public static final String FORMAT = "format"; public static final String IF = "if"; public static final String ELSE = "else"; public final static String FILE= "file"; public static final String DATE = "date"; public static final String NOW = "now"; public static final String DECIMAL = "decimal"; public static final String ID = "id"; public static final String PARAMS = "params"; public static final String TARGET = "target"; public static final String SINGLE = "single"; public static final String PAGING = "paging"; public static final String DESC = "desc"; public static final final String BREAK = "break"; public static final String CONTINUE = "continue"; public static final String COLLECTION = "collection"; public static final String VAR = "var"; public static final String EXECUTOR = "executor-1"; public static final String ROLLBACK_FLAG = "rollback-flag"; public static final String SERVICE = "service"; public static final String REF = "ref"; public static final String BIZS = "bizs"; public static final String TITLES = "titles"; public static final String COLUMNS = "columns"; public static final String CURRUSER = "currUser"; public static final String CURRPERM = "currPerm"; public static final String TASK_EXECUTOR = "taskExecutor"; public static final String DELIMITER = "delimiter"; public static final String OPERNAME = "operName"; } currParams.remove(varStr); currParams.remove(index); currParams.remove(index+"_"); }}Attach the pom file
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.rd</groupId> <artifactId>parser</artifactId> <packaging>jar</packaging> <version>1.0-SNAPSHOT</version> <name>myparser</name> <url>http://maven.apache.org</url> <dependencies> <dependency> <groupId>dom4j</groupId> <artifactId>dom4j</artifactId> <version>1.6.1</version> </dependency> <dependency> <groupId>opensymphony</groupId> <artifactId>ognl</artifactId> <version>2.6.11</version> </dependency> <dependency> <groupId>commons-collections</groupId> <artifactId>commons-collections</artifactId> <version>3.2.1</version> </dependency> <dependency> <groupId>commons-lang</groupId> <artifactId>commons-lang</artifactId> <version>2.6</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> <scope>test</scope> </dependency> </dependency> </dependencies> <build> <resources> <resource> <resource> <directory>src/main/java</directory> <includes> <include>**/*.xml</include> </includes> </resource> <resource> <directory>src/main/resources</directory> <includes> <include>**/*</include> </resource> </resources> </testResources> <testResources> <testResource> <directory>${project.basedir}/src/test/java</directory> </testResource> <testResource> <directory>${project.basedir}/src/test/resources</directory> </testResource> </testResources> <plugins> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.1</version> <configuration> <source>1.8</source> <target>1.8</target> <encoding>UTF-8</encoding> </configuration> </plugin> </plugins> </build></project>The above method to implement mybatis dynamic sql by yourself is all the content I have shared with you. I hope you can give you a reference and I hope you can support Wulin.com more.