Mybatis-SqlNode源码解析

Mybatis3.5.1源码分析

  1. Mybatis-SqlSessionFactoryBuilder,XMLConfigBuilder,XPathParser源码解析
  2. Mybatis-Configuration源码解析
  3. Mybatis-事务对象源码解析
  4. Mybatis-数据源源码解析
  5. Mybatis缓存策略源码解析
  6. Mybatis-DatabaseIdProvider源码解析
  7. Mybatis-TypeHandler源码解析
  8. Mybatis-Reflector源码解析
  9. Mybatis-ObjectFactory,ObjectWrapperFactory源码分析
  10. Mybatis-Mapper各类标签封装类源码解析
  11. Mybatis-XMLMapperBuilder,XMLStatmentBuilder源码分析
  12. Mybatis-MapperAnnotationBuilder源码分析
  13. [Mybatis-MetaObject,MetaClass源码解析]https://www.jianshu.com/p/f51fa552f30a)
  14. Mybatis-LanguageDriver源码解析
  15. Mybatis-SqlSource源码解析
  16. Mybatis-SqlNode源码解析
  17. Mybatis-KeyGenerator源码解析
  18. Mybatis-Executor源码解析
  19. Mybatis-ParameterHandler源码解析
  20. Mybatis-StatementHandler源码解析
  21. Mybatis-DefaultResultSetHandler(一)源码解析
  22. Mybatis-DefaultResultSetHandler(二)源码解析
  23. Mybatis-ResultHandler,Cursor,RowBounds 源码分析
  24. Mybatis-MapperProxy源码解析
  25. Mybatis-SqlSession源码解析
  26. Mybatis-Interceptor源码解析

SqlNode

作用于SqlSource

/**
 *    Copyright 2009-2015 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
 *
 *    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;

/**
 * 

* 各个类型的动态SQL标签的业务实现逻辑都会在该接口上实现 *

*

* 参考博客: *

    *
  1. https://blog.csdn.net/wp120453/article/details/93736335
  2. *
  3. https://www.jianshu.com/p/68f6bb7febd8
  4. *
*

* @author Clinton Begin */ public interface SqlNode { /** * 将解析出来的值应用到context中 * @param context 一个用于记录动态SQL语句解析结果的容器 * @return 是否应用到 {@code context} 中 */ boolean apply(DynamicContext context); }

DynamicContext

/**
 *    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
 *
 *    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.HashMap;
import java.util.Map;
import java.util.StringJoiner;

import ognl.OgnlContext;
import ognl.OgnlRuntime;
import ognl.PropertyAccessor;

import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;

/**
 * 主要用于记录解析动态SQL语句之后产生的SQL语句片段,可以认为它是一个用于记录动态SQL语句解析结果的容器
 * 
    *
  1. 设置 OGNL的属性访问器,实现ContextMap和ContextAccessor
  2. *
  3. 用StringJoiner sqlBuilder = new StringJoiner(" ")记录Sql,其实就是将之前解析的多个Sql片段进行合并。
  4. *
  5. 在每个SqlNode实现类中,会调用每个SqlNode的apply(DynamicContext context)方法,将SQL添加到StringJoiner中
  6. *
  7. DynamicContext中有一个内部类ContextMap用于记录传入的参数。若传入的参数不是Map类型时,会创建对应的MetaObject对象,并封装成ContextMap对象
  8. *
*

* 参考博客:https://blog.csdn.net/LHQJ1992/article/details/90320639 *

* @author Clinton Begin */ public class DynamicContext { /** * ParameterObject的KEY名 */ public static final String PARAMETER_OBJECT_KEY = "_parameter"; /** * databaseId的KEY名 */ public static final String DATABASE_ID_KEY = "_databaseId"; static { /** * 注册特定PropertyAccessor的方式:OgnlRuntime.setPropertyAccessor(clz,PropertyAccessor) * clz是Class对象,它可以是目标对象的具体Class,也可以是父类、甚至是目标对象实现的接口之一。 */ OgnlRuntime.setPropertyAccessor(ContextMap.class, new ContextAccessor()); } /** * 绑定关系 */ private final ContextMap bindings; /** * SQL脚本 *

* StringJoiner是Java8新出的一个类,用于构造由分隔符分隔的字符序列, * 并可选择性地从提供的前缀开始和以提供的后缀结尾。省的我们开发人员再次通过StringBuffer * 或者StingBuilder拼接。 *

*/ private final StringJoiner sqlBuilder = new StringJoiner(" "); /** * 唯一数字 */ private int uniqueNumber = 0; /** * * @param configuration mybatis全局配置信息 * @param parameterObject 参数对象 */ public DynamicContext(Configuration configuration, Object parameterObject) { //参数对象是Map时 if (parameterObject != null && !(parameterObject instanceof Map)) { //构造parameterObject的元对象 MetaObject metaObject = configuration.newMetaObject(parameterObject); //是否存在对parameterObject的TypeHandler boolean existsTypeHandler = configuration.getTypeHandlerRegistry().hasTypeHandler(parameterObject.getClass()); bindings = new ContextMap(metaObject, existsTypeHandler); } else { bindings = new ContextMap(null, false); } //添加parameteObject和databaseId到bindings bindings.put(PARAMETER_OBJECT_KEY, parameterObject); bindings.put(DATABASE_ID_KEY, configuration.getDatabaseId()); } /** * * @return {@see #bindings} */ public Map getBindings() { return bindings; } /** * 赋值给 {@see #bindings} * @param name bindings.key * @param value bindings.value */ public void bind(String name, Object value) { bindings.put(name, value); } /** * 添加SQL脚本到 {@link #sqlBuilder} * @param sql sql脚本 */ public void appendSql(String sql) { sqlBuilder.add(sql); } /** * 获取SQL脚本,去除了空格 * @return {@link #sqlBuilder}.toString().trim(); */ public String getSql() { return sqlBuilder.toString().trim(); } /** * 获取唯一数字,每获取一次 {@link #uniqueNumber} +1 * @return {@see #uniqueNumber} */ public int getUniqueNumber() { return uniqueNumber++; } /** * 上下文Map */ static class ContextMap extends HashMap { private static final long serialVersionUID = 2977601501966151582L; /** * 参数元对象 */ private final MetaObject parameterMetaObject; /** * 应变参数对象 */ private final boolean fallbackParameterObject; /** * * @param parameterMetaObject 参数元对象 * @param fallbackParameterObject 应变参数对象 */ public ContextMap(MetaObject parameterMetaObject, boolean fallbackParameterObject) { this.parameterMetaObject = parameterMetaObject; this.fallbackParameterObject = fallbackParameterObject; } @Override public Object get(Object key) { String strKey = (String) key; //如果key存在在容器中,直接返回 if (super.containsKey(strKey)) { return super.get(strKey); } //参数元对象为null,直接返回 if (parameterMetaObject == null) { return null; } //是应变参数对且没有在参数元对象像中找到对应strKey对应的Getter方法 if (fallbackParameterObject && !parameterMetaObject.hasGetter(strKey)) { return parameterMetaObject.getOriginalObject();//返回参数元对象的原始对象 } else { // issue #61 do not modify the context when reading 读取时不要修改上下文 //从参数元对象中获取值,支持strKey='order[0].item[0].name'的获取 return parameterMetaObject.getValue(strKey); } } } static class ContextAccessor implements PropertyAccessor { @Override public Object getProperty(Map context, Object target, Object name) { //这里的map其实就是ContextMap Map map = (Map) target; //直接获取contextMap中的keyvalue或者获取MetaObject中的getValue Object result = map.get(name); if (map.containsKey(name) || result != null) { return result; } //如参数对象本身是个map,直接获取 Object parameterObject = map.get(PARAMETER_OBJECT_KEY); if (parameterObject instanceof Map) { return ((Map)parameterObject).get(name); } return null; } /** * 设置属性 * @param context * @param target 其实就是ContextMap实例 * @param name 传给ContextMap实例的key * @param value 传给ContextMap实例的value */ @Override public void setProperty(Map context, Object target, Object name, Object value) { Map map = (Map) target; map.put(name, value); } @Override public String getSourceAccessor(OgnlContext arg0, Object arg1, Object arg2) { return null; } @Override public String getSourceSetter(OgnlContext arg0, Object arg1, Object arg2) { return null; } } }

ChooseSqlNode

/**
 *    Copyright 2009-2017 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
 *
 *    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;

/**
 * chooseSQL节点。
 * @author Clinton Begin
 */
public class ChooseSqlNode implements SqlNode {


  /**
   * otherwise标签
   */
  private final SqlNode defaultSqlNode;
  /**
   * when标签集合
   */
  private final List ifSqlNodes;

  /**
   *
   * @param ifSqlNodes  when标签集合
   * @param defaultSqlNode otherwise标签
   */
  public ChooseSqlNode(List ifSqlNodes, SqlNode defaultSqlNode) {
    this.ifSqlNodes = ifSqlNodes;
    this.defaultSqlNode = defaultSqlNode;
  }

  @Override
  public boolean apply(DynamicContext context) {
    // when 节点根据 test 表达式判断是否生效
    for (SqlNode sqlNode : ifSqlNodes) {
      if (sqlNode.apply(context)) {
        return true;
      }
    }
    // when 节点如果都未生效,且存在 otherwise 节点,则使用 otherwise 节点
    if (defaultSqlNode != null) {
      defaultSqlNode.apply(context);
      return true;
    }
    return false;
  }
}

ForEachSqlNode

/**
 *    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
 *
 *    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.Map;

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

/**
 * ForEach标签
 * @author Clinton Begin
 */
public class ForEachSqlNode implements SqlNode {
  /**
   * foreach的
   */
  public static final String ITEM_PREFIX = "__frch_";

  private final ExpressionEvaluator evaluator;
  /**
   *  foreach标签的collection属性,循环的集合对象名
   */
  private final String collectionExpression;
  /**
   *  forEach标签下的子节点,封装成MixedSqlNode
   */
  private final SqlNode contents;
  /**
   * foreach标签的open属性,foreach代码的开始符号,一般是(和close=")"合用。常用在in(),values()时。该参数可选。
   */
  private final String open;
  /**
   * foreach标签的close属性,foreach代码的关闭符号,一般是)和open="("合用。常用在in(),values()时。该参数可选。
   */
  private final String close;
  /**
   * foreach标签的separator属性,元素之间的分隔符,例如在in()的时候,separator=","会自动在元素中间用“,“隔开,避免手动输入逗号导致sql错误,如in(1,2,)这样。该参数可选
   */
  private final String separator;
  /**
   * foreach标签的item属性,集合对象的元素名
   */
  private final String item;
  /**
   * foreach标签的index属性,在list和数组中,index是元素的序号,在map中,index是元素的key,该参数可选。
   */
  private final String index;
  /**
   * mybatis全局配置信息
   */
  private final Configuration configuration;

  /**
   *
   * @param configuration mybatis全局配置信息
   * @param contents forEach标签下的子节点,封装成MixedSqlNode
   * @param collectionExpression foreach标签的collection属性,循环的集合对象名
   * @param index foreach标签的index属性,在list和数组中,index是元素的序号,在map中,index是元素的key,该参数可选。
   * @param item foreach标签的item属性,集合对象的元素名
   * @param open foreach标签的open属性,foreach代码的开始符号,一般是(和close=")"合用。常用在in(),values()时。该参数可选。
   * @param close foreach标签的close属性,foreach代码的关闭符号,一般是)和open="("合用。常用在in(),values()时。该参数可选。
   * @param separator foreach标签的separator属性,元素之间的分隔符,例如在in()的时候,separator=","会自动在元素中间用“,“隔开,避免手动输入逗号导致sql错误,如in(1,2,)这样。该参数可选
   */
  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 bindings = context.getBindings();
    //获取expression对应在parameterObject的对象的Iterable对象
    final Iterable iterable = evaluator.evaluateIterable(collectionExpression, bindings);
    //没有元素就直接返回true
    if (!iterable.iterator().hasNext()) {
      return true;
    }
    boolean first = true;
    //将open添加到context中
    applyOpen(context);
    // 迭代索引
    int i = 0;
    for (Object o : iterable) {
      DynamicContext oldContext = context;
      //对content进行装饰成prefixedContext
      if (first || separator == null) {
        context = new PrefixedContext(context, "");//首个元素不需要前缀,所以prefix为空字符串
      } else {
        context = new PrefixedContext(context, separator);
      }
      int uniqueNumber = context.getUniqueNumber();
      // Issue #709
      if (o instanceof Map.Entry) {
        // entry 集合项索引为 key,集合项为 value
        @SuppressWarnings("unchecked")
        Map.Entry mapEntry = (Map.Entry) o;
        applyIndex(context, mapEntry.getKey(), uniqueNumber);
        applyItem(context, mapEntry.getValue(), uniqueNumber);
      } else {
        // 绑定集合项索引关系
        applyIndex(context, i, uniqueNumber);
        // 绑定集合项关系
        applyItem(context, o, uniqueNumber);
      }
      // 对解析的表达式进行替换,如 idx = #{index} AND itm = #{item} 替换为 idx = #{__frch_index_1} AND itm = #{__frch_item_1}
      contents.apply(new FilteredDynamicContext(configuration, context, index, item, uniqueNumber));
      if (first) {
        //只要没有应用前缀,first就一直为true,正常情况下,这里到这一步,isPrefixApplied一般为false。
        first = !((PrefixedContext) context).isPrefixApplied();
      }
      context = oldContext;
      i++;
    }
    //将close添加到context中
    applyClose(context);
    //移除item绑定关系
    context.getBindings().remove(item);
    //移除index绑定关系
    context.getBindings().remove(index);
    return true;
  }

  /**
   * 绑定集合项索引关系
   * @param context PrefixedContext实例
   * @param o index值
   * @param i {@code context} 的唯一数字 context.getUniqueNumber()
   */
  private void applyIndex(DynamicContext context, Object o, int i) {
    if (index != null) {
      context.bind(index, o);
      context.bind(itemizeItem(index, i), o);
    }
  }

  /**
   * 绑定集合项关系
   * @param context PrefixedContext实例
   * @param o index值
   * @param i {@code context} 的唯一数字 context.getUniqueNumber()
   */
  private void applyItem(DynamicContext context, Object o, int i) {
    if (item != null) {
      context.bind(item, o);
      context.bind(itemizeItem(item, i), o);
    }
  }

  /**
   * 应用 {@link #open}
   * @param context 用于记录动态SQL语句解析结果的容器
   */
  private void applyOpen(DynamicContext context) {
    if (open != null) {
      context.appendSql(open);
    }
  }

  /**
   * 应用 {@link #close}
   * @param context  用于记录动态SQL语句解析结果的容器
   */
  private void applyClose(DynamicContext context) {
    if (close != null) {
      context.appendSql(close);
    }
  }

  /**
   * 组装列表项名
   * @param item 项名
   * @param i 位置
   * @return {@link #ITEM_PREFIX} + item + "_" + i
   */
  private static String itemizeItem(String item, int i) {
    return ITEM_PREFIX + item + "_" + i;
  }

  /**
   * 对解析的表达式进行替换的DynamincContext装饰类
   */
  private static class FilteredDynamicContext extends DynamicContext {
    /**
     * 委托类对象
     */
    private final DynamicContext delegate;
    /**
     * {@link #delegate} 的 uniqueNumber
     */
    private final int index;
    /**
     * foreach标签的index属性,在list和数组中,index是元素的序号,
     * 在map中,index是元素的key,该参数可选。
     */
    private final String itemIndex;
    /**
     * foreach标签的item属性,集合对象的元素名
     */
    private final String item;

    /**
     *
     * @param configuration mybatis全局配置信息
     * @param delegate 委托类对象
     * @param itemIndex foreach标签的index属性,在list和数组中,index是元素的序号,在map中,index是元素的key,该参数可选。
     * @param item foreach标签的item属性,集合对象的元素名
     * @param i {@code delegate} 的 uniqueNumber
     */
    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 getBindings() {
      return delegate.getBindings();
    }

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

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

    @Override
    public void appendSql(String sql) {
      GenericTokenParser parser = new GenericTokenParser("#{", "}", content -> {
        // 对解析的表达式进行替换,如 idx = #{index} AND itm = #{item} 替换为 idx = #{__frch_index_1} AND itm = #{__frch_item_1}
        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() {
      return delegate.getUniqueNumber();
    }

  }

  /**
   *  带prefix的记录动态SQL语句解析结果的容器
   */
  private class PrefixedContext extends DynamicContext {
    /**
     * 委托对象
     */
    private final DynamicContext delegate;
    /**
     * 前缀
     */
    private final String prefix;
    /**
     * 已经应用了前缀的标记
     */
    private boolean prefixApplied;

    /**
     *
     * @param delegate 委托对象
     * @param prefix 前缀
     */
    public PrefixedContext(DynamicContext delegate, String prefix) {
      super(configuration, null);
      this.delegate = delegate;
      this.prefix = prefix;
      this.prefixApplied = false;
    }

    /**
     * 是否已经应用了前缀的标记
     * @return
     */
    public boolean isPrefixApplied() {
      return prefixApplied;
    }

    @Override
    public Map getBindings() {
      return delegate.getBindings();
    }

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

    @Override
    public void appendSql(String sql) {
      //如果还没有添加prefix,且sql经过修剪后长度不为0,就会先添加prefix到delegate
      if (!prefixApplied && sql != null && sql.trim().length() > 0) {
        delegate.appendSql(prefix);
        prefixApplied = true;
      }
      //添加sql到delegate
      delegate.appendSql(sql);
    }

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

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

}

IfSqlNode

/**
 *    Copyright 2009-2017 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
 *
 *    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;

/**
 * if标签,动态 SQL 通常要做的事情是有条件地包含 where 子句的一部分
 * @author Clinton Begin
 */
public class IfSqlNode implements SqlNode {

  /**
   * OGNL 表达式计算工具
   */
  private final ExpressionEvaluator evaluator;
  /**
   * ognl表达式
   */
  private final String test;
  /**
   * if标签下的字节点,封装成MixedSqlNode
   */
  private final SqlNode contents;

  /**
   *
   * @param contents if标签下的字节点,封装成MixedSqlNode
   * @param test  ognl表达式
   */
  public IfSqlNode(SqlNode contents, String test) {
    this.test = test;
    this.contents = contents;
    this.evaluator = new ExpressionEvaluator();
  }

  @Override
  public boolean apply(DynamicContext context) {
    // 根据 test 表达式判断当前节点是否生效
    if (evaluator.evaluateBoolean(test, context.getBindings())) {
      contents.apply(context);
      return true;
    }
    return false;
  }

}

MixedSqlNode

/**
 *    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
 *
 *    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;

/**
 * 混合的SQL节点,里面存储着sqlNode结合,当调用apply方法时,会遍历其sqlNode集合的元素,调用元素的apply进行处理。
 * @author Clinton Begin
 */
public class MixedSqlNode implements SqlNode {
  private final List contents;

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

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

TrimSqlNode

/**
 *    Copyright 2009-2018 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
 *
 *    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.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.StringTokenizer;

import org.apache.ibatis.session.Configuration;

/**
 * Trim标签
 * @author Clinton Begin
 */
public class TrimSqlNode implements SqlNode {

  /**
   * trim标签下的字节点,封装成MixedSqlNode
   */
  private final SqlNode contents;
  /**
   * trim标签的prefix属性,给sql语句拼接的前缀
   */
  private final String prefix;
  /**
   * trim标签的suffix属性,给sql语句拼接的后缀
   */
  private final String suffix;
  /**
   * trim标签的prefixOverrides属性
   * 

* 去除sql语句前面的关键字或者字符,该关键字或者字符由prefixOverrides属性指定, * 假设该属性指定为"AND",当sql语句的开头为"AND",trim标签将会去除该"AND" *

*/ private final List prefixesToOverride; /** * trim标签的suffixOverride,去除sql语句后面的关键字或者字符,该关键字或者字符由suffixOverrides属性指定 */ private final List suffixesToOverride; /** * mybatis的全局配置信息 */ private final Configuration configuration; /** * * @param configuration mybatis的全局配置信息 * @param contents trim标签下的字节点,封装成MixedSqlNode * @param prefix trim标签的prefix属性 * @param prefixesToOverride trim标签的prefixOverrides属性 * @param suffix trim标签的suffix属性 * @param suffixesToOverride trim标签的suffixOverride */ public TrimSqlNode(Configuration configuration, SqlNode contents, String prefix, String prefixesToOverride, String suffix, String suffixesToOverride) { this(configuration, contents, prefix, parseOverrides(prefixesToOverride), suffix, parseOverrides(suffixesToOverride)); } /** * * @param configuration mybatis的全局配置信息 * @param contents trim标签下的字节点,封装成MixedSqlNode * @param prefix trim标签的prefix属性 * @param prefixesToOverride trim标签的prefixOverrides属性 * @param suffix trim标签的suffix属性 * @param suffixesToOverride trim标签的suffixOverride */ protected TrimSqlNode(Configuration configuration, SqlNode contents, String prefix, List prefixesToOverride, String suffix, List 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为DynamincContext的代理类,提供了根据suffix,prefix,suffixOverride,prefixOverride进行 * 对filteredDynamicContext修剪的方法。 */ FilteredDynamicContext filteredDynamicContext = new FilteredDynamicContext(context); //将解析出来的值添加到context中 boolean result = contents.apply(filteredDynamicContext); //根据suffix,prefix,suffixOverride,prefixOverride进行对filteredDynamicContext修剪 filteredDynamicContext.applyAll(); return result; } /** * 将prefixesToOverride属性或者suffixesToOverride属性值根据'|'分割成List<String>, * eg:prefixesToOverride='and | or' ==> List{AND,OR} * @param overrides prefixesToOverride属性或者suffixesToOverride属性 * @return 返回根据'|'分割成List<String>,如果overrides为null,会返回Collections的静态空集合对象 */ private static List parseOverrides(String overrides) { if (overrides != null) { final StringTokenizer parser = new StringTokenizer(overrides, "|", false); final List list = new ArrayList<>(parser.countTokens()); while (parser.hasMoreTokens()) { //将分割处理的token以大写的形式添加到list中 list.add(parser.nextToken().toUpperCase(Locale.ENGLISH)); } return list; } //返回Collections的静态空集合对象 return Collections.emptyList(); } /** * DynamincContext的代理类,提供了根据suffix,prefix,suffixOverride,prefixOverride进行 * 对filteredDynamicContext修剪的方法。 */ private class FilteredDynamicContext extends DynamicContext { /** * DynamicContext委托对象 */ private DynamicContext delegate; /** * 已经应用了前缀的标记 */ private boolean prefixApplied; /** * 已经应用了后缀的标记 */ private boolean suffixApplied; /** * sql脚本 */ private StringBuilder sqlBuffer; /** * * @param delegate DynamicContext委托对象 */ public FilteredDynamicContext(DynamicContext delegate) { super(configuration, null); this.delegate = delegate; this.prefixApplied = false; this.suffixApplied = false; this.sqlBuffer = new StringBuilder(); } /** * 对sqlBuffer根据suffix,prefix,suffixOverride,prefixOverride进行修剪 */ public void applyAll() { sqlBuffer = new StringBuilder(sqlBuffer.toString().trim()); String trimmedUppercaseSql = sqlBuffer.toString().toUpperCase(Locale.ENGLISH); if (trimmedUppercaseSql.length() > 0) { applyPrefix(sqlBuffer, trimmedUppercaseSql); applySuffix(sqlBuffer, trimmedUppercaseSql); } delegate.appendSql(sqlBuffer.toString()); } @Override public Map getBindings() { return delegate.getBindings(); } @Override public void bind(String name, Object value) { delegate.bind(name, value); } @Override public int getUniqueNumber() { return delegate.getUniqueNumber(); } @Override public void appendSql(String sql) { sqlBuffer.append(sql); } @Override public String getSql() { return delegate.getSql(); } /** * 应用前缀 * @param sql sql脚本 * @param trimmedUppercaseSql 去掉了 {@code sql} 的前和尾的所有空格,以及全部字母转换成了大写的字符串 */ private void applyPrefix(StringBuilder sql, String trimmedUppercaseSql) { if (!prefixApplied) { prefixApplied = true; if (prefixesToOverride != null) { for (String toRemove : prefixesToOverride) { //是否以prefiexesToOverride的元素开头,如果是,就以从0位置开始删除。只会执行一下删除 if (trimmedUppercaseSql.startsWith(toRemove)) { sql.delete(0, toRemove.trim().length()); break; } } } //插入prefix到sql的开头 if (prefix != null) { //因为插入的位置是是sql的开头,所以要先插入空格 sql.insert(0, " "); sql.insert(0, prefix); } } } /** * 应用后缀 * @param sql sql脚本 * @param trimmedUppercaseSql 去掉了 {@code sql} 的前和尾的所有空格,以及全部字母转换成了大写的字符串 */ private void applySuffix(StringBuilder sql, String trimmedUppercaseSql) { if (!suffixApplied) { suffixApplied = true; if (suffixesToOverride != null) { for (String toRemove : suffixesToOverride) { //删除在sql中以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; } } } //插入suffix到sql的结尾 if (suffix != null) { //因为插入的位置是sql的结尾,所以要先插入空格 sql.append(" "); sql.append(suffix); } } } } }

SetSqlNode

/**
 *    Copyright 2009-2018 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
 *
 *    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.Collections;
import java.util.List;

import org.apache.ibatis.session.Configuration;

/**
 * set 元素会动态前置 SET 关键字,同时也会消除无关的逗号,
 * 因为用了条件语句之后很可能就会在生成的赋值语句的后面留下这些逗号。
 * 

* 实现逻辑是套用TrimSqlNode *

* @author Clinton Begin */ public class SetSqlNode extends TrimSqlNode { private static final List COMMA = Collections.singletonList(","); /** * * @param configuration mybatis全局配置信息 * @param contents set标签下的字节点,封装成MixedSqlNode */ public SetSqlNode(Configuration configuration,SqlNode contents) { /** * 设置为'SET'为前缀,设置COMMA为trimSqlNode的prefixOverride,到时候调用apply * 方法就会自动添加'SET'作为前缀,并覆盖掉'SET'后面COMMA的元素 */ super(configuration, contents, "SET", COMMA, null, COMMA); } }

StaticTextSqlNode

/**
 *    Copyright 2009-2017 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
 *
 *    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;

/**
 * 静态文本SQL节点,其apply方法使得只是将存储的文本内容直接添加到DynamicContext对象中
 * @author Clinton Begin
 */
public class StaticTextSqlNode implements SqlNode {
  /**
   * 静态文本
   */
  private final String text;

  /**
   *
   * @param text 静态文本
   */
  public StaticTextSqlNode(String text) {
    this.text = text;
  }

  /**
   * 将{@link #text} 直接添加到 {@code context}
   * @param context 一个用于记录动态SQL语句解析结果的容器
   * @return 写死返回true
   */
  @Override
  public boolean apply(DynamicContext context) {
    context.appendSql(text);
    return true;
  }

}

TextSqlNode

/**
 *    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
 *
 *    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.regex.Pattern;

import org.apache.ibatis.parsing.GenericTokenParser;
import org.apache.ibatis.parsing.TokenHandler;
import org.apache.ibatis.scripting.ScriptingException;
import org.apache.ibatis.type.SimpleTypeRegistry;

/**
 * 文本SQL节点。用传入的实际参数对象中的属性值对${}进行直接替换,并且不会进行任何检查!
 * @author Clinton Begin
 */
public class TextSqlNode implements SqlNode {
  /**
   * 文本内容
   */
  private final String text;
  /**
   * 需要匹配的正则表达式
   */
  private final Pattern injectionFilter;

  /**
   *
   * @param text 文本内容
   */
  public TextSqlNode(String text) {
    this(text, null);
  }

  /**
   *
   * @param text 文本内容
   * @param injectionFilter 需要匹配的正则表达式
   */
  public TextSqlNode(String text, Pattern injectionFilter) {
    this.text = text;
    this.injectionFilter = injectionFilter;
  }

  /**
   * 是否动态SQL。当sql中包含有${}时,就认为是动态SQL
   */
  public boolean isDynamic() {
    //DynamicCheckerTokenParse:当sql中包含有${}时,就认为是动态SQL
    DynamicCheckerTokenParser checker = new DynamicCheckerTokenParser();
    GenericTokenParser parser = createParser(checker);
    //GenericTokenParser会将检查到的'${XXX}'的XXX传递给DynamicCheckerTokenParser的handleToken方法进行处理.
    parser.parse(text);
    return checker.isDynamic();
  }

  /**
   * 用传入的实际参数对象中的属性值对${}进行直接替换,并且不会进行任何检查!
   * @param context 用于记录动态SQL语句解析结果的容器
   * @return 写死了true
   */
  @Override
  public boolean apply(DynamicContext context) {

    GenericTokenParser parser = createParser(new BindingTokenParser(context, injectionFilter));
    /**
     * GenericTokenParser会将检查到的'${XXX}'的XXX传递给BindingTokenParser的handleToken方法进行处理.
     * 将解析处理的结果添加到context中。
     */
    context.appendSql(parser.parse(text));
    return true;
  }

  /**
   * 创建通用token解析器
   * @param handler token处理器
   * @return 专门处理'${...}'形式的token的通用token解析器
   */
  private GenericTokenParser createParser(TokenHandler handler) {
    return new GenericTokenParser("${", "}", handler);
  }

  /**
   * 用于获取替换${}的真实参数值
   */
  private static class BindingTokenParser implements TokenHandler {

    private DynamicContext context;
    /**
     * 正则表达式
     */
    private Pattern injectionFilter;

    public BindingTokenParser(DynamicContext context, Pattern injectionFilter) {
      this.context = context;
      this.injectionFilter = injectionFilter;
    }

    @Override
    public String handleToken(String content) {
      //TODO 没有看懂这里干嘛的
      Object parameter = context.getBindings().get("_parameter");
      if (parameter == null) {
        context.getBindings().put("value", null);
      } else if (SimpleTypeRegistry.isSimpleType(parameter.getClass())) {
        context.getBindings().put("value", parameter);
      }
      // 从传入的参数中获取到${}对应的值并对${}进行替换
      Object value = OgnlCache.getValue(content, context.getBindings());
      String srtValue = value == null ? "" : String.valueOf(value); // issue #274 return "" instead of "null"
      //检查值是否匹配正则表达式,不匹配抛出异常
      checkInjection(srtValue);
      return srtValue;
    }

    /**
     * 检查值是否匹配正则表达式,不匹配抛出异常
     * @param value 值
     */
    private void checkInjection(String value) {
      if (injectionFilter != null && !injectionFilter.matcher(value).matches()) {
        throw new ScriptingException("Invalid input. Please conform to regex" + injectionFilter.pattern());
      }
    }
  }

  /**
   *  用于sql的动态检测,当sql中包含有${}时就认为是动态SQL
   */
  private static class DynamicCheckerTokenParser implements TokenHandler {

    /**
     * 是否动态SQL,当sql中包含有${}时就认为是动态SQL
     */
    private boolean isDynamic;

    public DynamicCheckerTokenParser() {
      // Prevent Synthetic Access
    }

    /**
     *
     * @return {@see #isDynamic}
     */
    public boolean isDynamic() {
      return isDynamic;
    }

    /**
     * 当GenericTokenParser检测到sql中包含有${}时,只是简单的标记为动态
     */
    @Override
    public String handleToken(String content) {
      this.isDynamic = true;
      return null;
    }
  }

}

VarDecSqlNode

/**
 *    Copyright 2009-2015 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
 *
 *    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;

/**
 * OGNL SqlNode
 * @author Frank D. Martinez [mnesarco]
 */
public class VarDeclSqlNode implements SqlNode {

  /**
   * 变量名
   */
  private final String name;
  /**
   * 表达式
   */
  private final String expression;

  /**
   *
   * @param var 变量名
   * @param exp 表达式
   */
  public VarDeclSqlNode(String var, String exp) {
    name = var;
    expression = exp;
  }

  @Override
  public boolean apply(DynamicContext context) {
    final Object value = OgnlCache.getValue(expression, context.getBindings());
    context.bind(name, value);
    return true;
  }

}

WhereSqlNode

/**
 *    Copyright 2009-2015 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
 *
 *    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.Arrays;
import java.util.List;

import org.apache.ibatis.session.Configuration;

/**
 * where 标签知道只有在一个以上的if条件有值的情况下才去插入"WHERE"子句。
 * 而且,若最后的内容是"AND"或"OR"开头的,where 元素也知道如何将他们去除。
 * 

* 实现逻辑是套用TrimSqlNode *

* @author Clinton Begin */ public class WhereSqlNode extends TrimSqlNode { private static List prefixList = Arrays.asList("AND ","OR ","AND\n", "OR\n", "AND\r", "OR\r", "AND\t", "OR\t"); /** * * @param configuration mybatis全局配置信息 * @param contents where标签下的字节点,封装成MixedSqlNode */ public WhereSqlNode(Configuration configuration, SqlNode contents) { /** * 设置为'WHERE'为前缀,设置prefixList为trimSqlNode的prefixOverride,到时候调用apply * 方法就会自动添加'WHERE'作为前缀,并覆盖掉'WHERE'后面prefixList的元素 */ super(configuration, contents, "WHERE", prefixList, null, null); } }

你可能感兴趣的:(Mybatis-SqlNode源码解析)