Previously there was a share of Mybatis application 9 dynamic SQL tags. So how does this tag work in practice?

SqlNode

SQL > update,trim,if; SQL > update,trim,if

package org.apache.ibatis.scripting.xmltags;

/** * SQL node information **@author Clinton Begin
 */
public interface SqlNode {
    /** * parse the node **@paramContext contains input information for the Mapper call, as well as built-in _parameter and _databaseId parameter information * *@return* /
    boolean apply(DynamicContext context);
}
Copy the code

MixedSqlNode

Where SQL (< INSERT >,< DELETE >,< UPDATE >,< SELECT >) tags in the XML are converted to a MixedSqlNode object. MixedSqlNode has a property contents in it. Contains all dynamic tags for SQL tags (< WHERE >,<if>…) Information is then used to parse SQL statements based on dynamic label information. So what he does is he iterates through the contents property to parse the other property tags

/** * Copyright 2009-2019 the original author or authors. * 

* Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at *

* http://www.apache.org/licenses/LICENSE-2.0 * < p > * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */

package org.apache.ibatis.scripting.xmltags; import java.util.List; /** * Describes a group of SqlNode objects * <select ID ="getStudentsByAlias" resultType="studentDto" useCache="false"> * select * id,name.age,hobby * from student * <where> * <if test="1==1"> * and id is not null * </if> * </where> * </select> * This will be resolved to a StaticTextSqlNode and a WhereSqlNode. The SQL representing the WHERE tag includes the SQL information for the nested IF tag *@author Clinton Begin */ public class MixedSqlNode implements SqlNode { private final List<SqlNode> contents; public MixedSqlNode(List<SqlNode> contents) { this.contents = contents; } @Override public boolean apply(DynamicContext context) { // Call all SqlNode apply methods contents.forEach(node -> node.apply(context)); return true; }}Copy the code

StaticTextSqlNode

Append text content directly

/** * Copyright 2009-2017 the original author or authors. * <p> * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * <p> * http://www.apache.org/licenses/LICENSE-2.0 * < p > * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the  License. */ package org.apache.ibatis.scripting.xmltags; ** @author Clinton Begin */ public class StaticTextSqlNode implements SqlNode {private final String text; public StaticTextSqlNode(String text) { this.text = text; } @override public Boolean apply(DynamicContext context) {appendSql(text); return true; }}Copy the code

TrimSqlNode

TrimSqlNode has two subclasses. SetSqlNode and WhereSqlNode. The trim tag does the same for the set WHERE tag.

package org.apache.ibatis.scripting.xmltags;

import org.apache.ibatis.session.Configuration;

import java.util.*;

/** * parse the <trim> tag **@author Clinton Begin
 */
public class TrimSqlNode implements SqlNode {

    private final SqlNode contents;
    private final String prefix;
    private final String suffix;
    private final List<String> prefixesToOverride;
    private final List<String> suffixesToOverride;
    private final Configuration configuration;

    public TrimSqlNode(Configuration configuration, SqlNode contents, String prefix, String prefixesToOverride, String suffix, String suffixesToOverride) {
        this(configuration, contents, prefix, parseOverrides(prefixesToOverride), suffix, parseOverrides(suffixesToOverride));
    }

    protected TrimSqlNode(Configuration configuration, SqlNode contents, String prefix, List<String> prefixesToOverride, String suffix, List<String> suffixesToOverride) {
        this.contents = contents;
        this.prefix = prefix;
        this.prefixesToOverride = prefixesToOverride;
        this.suffix = suffix;
        this.suffixesToOverride = suffixesToOverride;
        this.configuration = configuration;
    }

    @Override
    public boolean apply(DynamicContext context) {
        FilteredDynamicContext filteredDynamicContext = new FilteredDynamicContext(context);
        // Where set trim All three types of labels may contain child labels, so these labels are stored as MixedSqlNode types. I need to do a second analysis of this
        boolean result = contents.apply(filteredDynamicContext);
        // Prefix and suffix substitutions
        filteredDynamicContext.applyAll();
        return result;
    }

    private static List<String> parseOverrides(String overrides) {
        if(overrides ! =null) {
            final StringTokenizer parser = new StringTokenizer(overrides, "|".false);
            final List<String> list = new ArrayList<>(parser.countTokens());
            while (parser.hasMoreTokens()) {
                list.add(parser.nextToken().toUpperCase(Locale.ENGLISH));
            }
            return list;
        }
        return Collections.emptyList();
    }

    private class FilteredDynamicContext extends DynamicContext {
        private DynamicContext delegate;
        private boolean prefixApplied;
        private boolean suffixApplied;
        private StringBuilder sqlBuffer;

        public FilteredDynamicContext(DynamicContext delegate) {
            super(configuration, null);
            this.delegate = delegate;
            this.prefixApplied = false;
            this.suffixApplied = false;
            this.sqlBuffer = new StringBuilder();
        }

        public void applyAll(a) {
            sqlBuffer = new StringBuilder(sqlBuffer.toString().trim());
            String trimmedUppercaseSql = sqlBuffer.toString().toUpperCase(Locale.ENGLISH);
            if (trimmedUppercaseSql.length() > 0) {
                // Prefix substitution
                applyPrefix(sqlBuffer, trimmedUppercaseSql);
                // suffix substitution
                applySuffix(sqlBuffer, trimmedUppercaseSql);
            }
            delegate.appendSql(sqlBuffer.toString());
        }

        @Override
        public Map<String, Object> getBindings(a) {
            return delegate.getBindings();
        }

        @Override
        public void bind(String name, Object value) {
            delegate.bind(name, value);
        }

        @Override
        public int getUniqueNumber(a) {
            return delegate.getUniqueNumber();
        }

        @Override
        public void appendSql(String sql) {
            sqlBuffer.append(sql);
        }

        @Override
        public String getSql(a) {
            return delegate.getSql();
        }

        private void applyPrefix(StringBuilder sql, String trimmedUppercaseSql) {
            if(! prefixApplied) { prefixApplied =true;
                if(prefixesToOverride ! =null) {
                    for (String toRemove : prefixesToOverride) {
                        if (trimmedUppercaseSql.startsWith(toRemove)) {
                            sql.delete(0, toRemove.trim().length());
                            break; }}}if(prefix ! =null) {
                    sql.insert(0."");
                    sql.insert(0, prefix); }}}private void applySuffix(StringBuilder sql, String trimmedUppercaseSql) {
            if(! suffixApplied) { suffixApplied =true;
                if(suffixesToOverride ! =null) {
                    for (String toRemove : suffixesToOverride) {
                        if (trimmedUppercaseSql.endsWith(toRemove) || trimmedUppercaseSql.endsWith(toRemove.trim())) {
                            int start = sql.length() - toRemove.trim().length();
                            int end = sql.length();
                            sql.delete(start, end);
                            break; }}}if(suffix ! =null) {
                    sql.append("");
                    sql.append(suffix);
                }
            }
        }

    }

}
Copy the code

ForeachSqlNode

Parse the <foreach> tag

package org.apache.ibatis.scripting.xmltags;

import org.apache.ibatis.parsing.GenericTokenParser;
import org.apache.ibatis.session.Configuration;

import java.util.Map;

/** * parse the <foreach> tag **@author Clinton Begin
 */
public class ForEachSqlNode implements SqlNode {
    public static final String ITEM_PREFIX = "__frch_";

    private final ExpressionEvaluator evaluator;
    private final String collectionExpression;
    private final SqlNode contents;
    private final String open;
    private final String close;
    private final String separator;
    private final String item;
    private final String index;
    private final Configuration configuration;

    public ForEachSqlNode(Configuration configuration, SqlNode contents, String collectionExpression, String index, String item, String open, String close, String separator) {
        this.evaluator = new ExpressionEvaluator();
        this.collectionExpression = collectionExpression;
        this.contents = contents;
        this.open = open;
        this.close = close;
        this.separator = separator;
        this.index = index;
        this.item = item;
        this.configuration = configuration;
    }

    @Override
    public boolean apply(DynamicContext context) {
        Map<String, Object> bindings = context.getBindings();
        // Get an iterator to configure the collection parameter collection
        finalIterable<? > iterable = evaluator.evaluateIterable(collectionExpression, bindings);if(! iterable.iterator().hasNext()) {return true;
        }
        boolean first = true;
        // Add the open attribute
        applyOpen(context);
        int i = 0;
        for (Object o : iterable) {
            DynamicContext oldContext = context;
            // The first one does not add separator
            if (first || separator == null) {
                context = new PrefixedContext(context, "");
            } else {
                context = new PrefixedContext(context, separator);
            }
            int uniqueNumber = context.getUniqueNumber();
            // Issue #709
            // Store the iterated values in the map
            if (o instanceof Map.Entry) {
                @SuppressWarnings("unchecked")
                Map.Entry<Object, Object> mapEntry = (Map.Entry<Object, Object>) o;
                applyIndex(context, mapEntry.getKey(), uniqueNumber);
                applyItem(context, mapEntry.getValue(), uniqueNumber);
            } else {
                applyIndex(context, i, uniqueNumber);
                applyItem(context, o, uniqueNumber);
            }
            // there are other dynamic tags in 
      
            contents.apply(new FilteredDynamicContext(configuration, context, index, item, uniqueNumber));
            if(first) { first = ! ((PrefixedContext) context).isPrefixApplied(); } context = oldContext; i++; }// Add the close attribute
        applyClose(context);
        // Clear the collection properties of the binding
        context.getBindings().remove(item);
        context.getBindings().remove(index);
        return true;
    }

    private void applyIndex(DynamicContext context, Object o, int i) {
        if(index ! =null) { context.bind(index, o); context.bind(itemizeItem(index, i), o); }}private void applyItem(DynamicContext context, Object o, int i) {
        if(item ! =null) { context.bind(item, o); context.bind(itemizeItem(item, i), o); }}private void applyOpen(DynamicContext context) {
        if(open ! =null) { context.appendSql(open); }}private void applyClose(DynamicContext context) {
        if(close ! =null) { context.appendSql(close); }}private static String itemizeItem(String item, int i) {
        return ITEM_PREFIX + item + "_" + i;
    }

    private static class FilteredDynamicContext extends DynamicContext {
        private final DynamicContext delegate;
        private final int index;
        private final String itemIndex;
        private final String item;

        public FilteredDynamicContext(Configuration configuration, DynamicContext delegate, String itemIndex, String item, int i) {
            super(configuration, null);
            this.delegate = delegate;
            this.index = i;
            this.itemIndex = itemIndex;
            this.item = item;
        }

        @Override
        public Map<String, Object> getBindings(a) {
            return delegate.getBindings();
        }

        @Override
        public void bind(String name, Object value) {
            delegate.bind(name, value);
        }

        @Override
        public String getSql(a) {
            return delegate.getSql();
        }

        @Override
        public void appendSql(String sql) {
            GenericTokenParser parser = new GenericTokenParser("# {"."}", content -> {
                String newContent = content.replaceFirst("^\s*" + item + "(? ! [^.,:\s])", itemizeItem(item, index));
                if(itemIndex ! =null && newContent.equals(content)) {
                    newContent = content.replaceFirst("^\s*" + itemIndex + "(? ! [^.,:\s])", itemizeItem(itemIndex, index));
                }
                return "# {" + newContent + "}";
            });

            delegate.appendSql(parser.parse(sql));
        }

        @Override
        public int getUniqueNumber(a) {
            returndelegate.getUniqueNumber(); }}private class PrefixedContext extends DynamicContext {
        private final DynamicContext delegate;
        private final String prefix;
        private boolean prefixApplied;

        public PrefixedContext(DynamicContext delegate, String prefix) {
            super(configuration, null);
            this.delegate = delegate;
            this.prefix = prefix;
            this.prefixApplied = false;
        }

        public boolean isPrefixApplied(a) {
            return prefixApplied;
        }

        @Override
        public Map<String, Object> getBindings(a) {
            return delegate.getBindings();
        }

        @Override
        public void bind(String name, Object value) {
            delegate.bind(name, value);
        }

        @Override
        public void appendSql(String sql) {
            if(! prefixApplied && sql ! =null && sql.trim().length() > 0) {
                delegate.appendSql(prefix);
                prefixApplied = true;
            }
            delegate.appendSql(sql);
        }

        @Override
        public String getSql(a) {
            return delegate.getSql();
        }

        @Override
        public int getUniqueNumber(a) {
            returndelegate.getUniqueNumber(); }}}Copy the code

VarDeclSqlNode

Parsing of the <bind> tag

package org.apache.ibatis.scripting.xmltags; ** @author Frank D. Martinez [mnesarco] */ public class VarDeclSqlNode implements SqlNode {private final String name; private final String expression; public VarDeclSqlNode(String var, String exp) { name = var; expression = exp; } @override public Boolean apply(DynamicContext context) {final Object value = OgnlCache.getValue(expression, context.getBindings()); #{likeName} context.bind(name, value); return true; }}Copy the code

ChooseSqlNode

Parse the Choose When Otherwise tag

package org.apache.ibatis.scripting.xmltags;

import java.util.List;

/** * parse the <choose> tag **@author Clinton Begin
 */
public class ChooseSqlNode implements SqlNode {
    /**
      
        Tag content */
      
    private final SqlNode defaultSqlNode;
    /**
      
        Tag collection */
      
    private final List<SqlNode> ifSqlNodes;

    public ChooseSqlNode(List<SqlNode> ifSqlNodes, SqlNode defaultSqlNode) {
        this.ifSqlNodes = ifSqlNodes;
        this.defaultSqlNode = defaultSqlNode;
    }

    @Override
    public boolean apply(DynamicContext context) {
        // The condition under the 
      
        tag has been converted to IfSqlNode and will be returned if the condition is met
      
        for (SqlNode sqlNode : ifSqlNodes) {
            if (sqlNode.apply(context)) {
                return true; }}// If the condition is not met, the last 
      
        is selected to concatenate the SQL
      
        if(defaultSqlNode ! =null) {
            defaultSqlNode.apply(context);
            return true;
        }
        return false; }}Copy the code

IfSqlNode

Parse SQL tags. The value under the if tag is parsed to determine if the condition is met

package org.apache.ibatis.scripting.xmltags;

/** * parse the <if> tag **@author Clinton Begin
 */
public class IfSqlNode implements SqlNode {
    /** * is used to parse the OGNL expression */
    private final ExpressionEvaluator evaluator;
    /** * Save the test attribute */ in the 
      
        tag
      
    private final String test;
    /** * save the SQL information in 
      
        tag */
      
    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 the OGNL expression resolves to true, the APPLY method of the SqlNode corresponding to the 
      
        tag content is called
      
        if (evaluator.evaluateBoolean(test, context.getBindings())) {
            contents.apply(context);
            return true;
        }
        return false; }}Copy the code