Mybatis-SqlNode源碼解析

Mybatis3.5.1源碼分析

  1. Mybatis-SqlSessionFactoryBuilder淑掌,XMLConfigBuilder,XPathParser源碼解析
  2. Mybatis-Configuration源碼解析
  3. Mybatis-事務(wù)對(duì)象源碼解析
  4. Mybatis-數(shù)據(jù)源源碼解析
  5. Mybatis緩存策略源碼解析
  6. Mybatis-DatabaseIdProvider源碼解析
  7. Mybatis-TypeHandler源碼解析
  8. Mybatis-Reflector源碼解析
  9. Mybatis-ObjectFactory达椰,ObjectWrapperFactory源碼分析
  10. Mybatis-Mapper各類標(biāo)簽封裝類源碼解析
  11. Mybatis-XMLMapperBuilder,XMLStatmentBuilder源碼分析
  12. Mybatis-MapperAnnotationBuilder源碼分析
  13. [Mybatis-MetaObject,MetaClass源碼解析]http://www.reibang.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;

/**
 * <p>
 *     各個(gè)類型的動(dòng)態(tài)SQL標(biāo)簽的業(yè)務(wù)實(shí)現(xiàn)邏輯都會(huì)在該接口上實(shí)現(xiàn)
 * </p>
 * <p>
 * 參考博客:
 * <ol>
 *     <li><a >https://blog.csdn.net/wp120453/article/details/93736335</a></li>
 *     <li><a href='http://www.reibang.com/p/68f6bb7febd8'>http://www.reibang.com/p/68f6bb7febd8</a></li>
 * </ol>
 * </p>
 * @author Clinton Begin
 */
public interface SqlNode {
  /**
   * 將解析出來(lái)的值應(yīng)用到context中
   * @param context 一個(gè)用于記錄動(dòng)態(tài)SQL語(yǔ)句解析結(jié)果的容器
   * @return 是否應(yīng)用到 {@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;

/**
 * 主要用于記錄解析動(dòng)態(tài)SQL語(yǔ)句之后產(chǎn)生的SQL語(yǔ)句片段,可以認(rèn)為它是一個(gè)用于記錄動(dòng)態(tài)SQL語(yǔ)句解析結(jié)果的容器
 * <ol>
 *     <li>設(shè)置 OGNL的屬性訪問(wèn)器项乒,實(shí)現(xiàn)ContextMap和ContextAccessor</li>
 *     <li>用StringJoiner sqlBuilder = new StringJoiner(" ")記錄Sql啰劲,其實(shí)就是將之前解析的多個(gè)Sql片段進(jìn)行合并。</li>
 *     <li>在每個(gè)SqlNode實(shí)現(xiàn)類中檀何,會(huì)調(diào)用每個(gè)SqlNode的apply(DynamicContext context)方法蝇裤,將SQL添加到StringJoiner中</li>
 *     <li>DynamicContext中有一個(gè)內(nèi)部類ContextMap用于記錄傳入的參數(shù)。若傳入的參數(shù)不是Map類型時(shí)频鉴,會(huì)創(chuàng)建對(duì)應(yīng)的MetaObject對(duì)象栓辜,并封裝成ContextMap對(duì)象</li>
 * </ol>
 * <p>
 *     參考博客:<a >https://blog.csdn.net/LHQJ1992/article/details/90320639</a>
 * </p>
 * @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 {
    /**
     * 注冊(cè)特定PropertyAccessor的方式:OgnlRuntime.setPropertyAccessor(clz,PropertyAccessor)
     * clz是Class對(duì)象,它可以是目標(biāo)對(duì)象的具體Class垛孔,也可以是父類藕甩、甚至是目標(biāo)對(duì)象實(shí)現(xiàn)的接口之一。
     */
    OgnlRuntime.setPropertyAccessor(ContextMap.class, new ContextAccessor());
  }

  /**
   * 綁定關(guān)系
   */
  private final ContextMap bindings;
  /**
   * SQL腳本
   * <p>
   * StringJoiner是Java8新出的一個(gè)類周荐,用于構(gòu)造由分隔符分隔的字符序列狭莱,
   * 并可選擇性地從提供的前綴開(kāi)始和以提供的后綴結(jié)尾悯姊。省的我們開(kāi)發(fā)人員再次通過(guò)StringBuffer
   * 或者StingBuilder拼接。
   * </p>
   */
  private final StringJoiner sqlBuilder = new StringJoiner(" ");
  /**
   * 唯一數(shù)字
   */
  private int uniqueNumber = 0;

  /**
   *
   * @param configuration mybatis全局配置信息
   * @param parameterObject 參數(shù)對(duì)象
   */
  public DynamicContext(Configuration configuration, Object parameterObject) {
    //參數(shù)對(duì)象是Map時(shí)
    if (parameterObject != null && !(parameterObject instanceof Map)) {
      //構(gòu)造parameterObject的元對(duì)象
      MetaObject metaObject = configuration.newMetaObject(parameterObject);
      //是否存在對(duì)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<String, Object> 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();
  }

  /**
   * 獲取唯一數(shù)字,每獲取一次 {@link #uniqueNumber} +1
   * @return {@see #uniqueNumber}
   */
  public int getUniqueNumber() {
    return uniqueNumber++;
  }

  /**
   * 上下文Map
   */
  static class ContextMap extends HashMap<String, Object> {
    private static final long serialVersionUID = 2977601501966151582L;
    /**
     * 參數(shù)元對(duì)象
     */
    private final MetaObject parameterMetaObject;
    /**
     * 應(yīng)變參數(shù)對(duì)象
     */
    private final boolean fallbackParameterObject;

    /**
     *
     * @param parameterMetaObject 參數(shù)元對(duì)象
     * @param fallbackParameterObject  應(yīng)變參數(shù)對(duì)象
     */
    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);
      }
      //參數(shù)元對(duì)象為null悯许,直接返回
      if (parameterMetaObject == null) {
        return null;
      }

      //是應(yīng)變參數(shù)對(duì)且沒(méi)有在參數(shù)元對(duì)象像中找到對(duì)應(yīng)strKey對(duì)應(yīng)的Getter方法
      if (fallbackParameterObject && !parameterMetaObject.hasGetter(strKey)) {
        return parameterMetaObject.getOriginalObject();//返回參數(shù)元對(duì)象的原始對(duì)象
      } else {
        // issue #61 do not modify the context when reading 讀取時(shí)不要修改上下文
        //從參數(shù)元對(duì)象中獲取值,支持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其實(shí)就是ContextMap
      Map map = (Map) target;
      //直接獲取contextMap中的keyvalue或者獲取MetaObject中的getValue
      Object result = map.get(name);
      if (map.containsKey(name) || result != null) {
        return result;
      }
      //如參數(shù)對(duì)象本身是個(gè)map,直接獲取
      Object parameterObject = map.get(PARAMETER_OBJECT_KEY);
      if (parameterObject instanceof Map) {
        return ((Map)parameterObject).get(name);
      }

      return null;
    }

    /**
     * 設(shè)置屬性
     * @param context
     * @param target 其實(shí)就是ContextMap實(shí)例
     * @param name 傳給ContextMap實(shí)例的key
     * @param value 傳給ContextMap實(shí)例的value
     */
    @Override
    public void setProperty(Map context, Object target, Object name, Object value) {
      Map<Object, Object> map = (Map<Object, Object>) 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節(jié)點(diǎn)辉阶。
 * @author Clinton Begin
 */
public class ChooseSqlNode implements SqlNode {


  /**
   * otherwise標(biāo)簽
   */
  private final SqlNode defaultSqlNode;
  /**
   * when標(biāo)簽集合
   */
  private final List<SqlNode> ifSqlNodes;

  /**
   *
   * @param ifSqlNodes  when標(biāo)簽集合
   * @param defaultSqlNode otherwise標(biāo)簽
   */
  public ChooseSqlNode(List<SqlNode> ifSqlNodes, SqlNode defaultSqlNode) {
    this.ifSqlNodes = ifSqlNodes;
    this.defaultSqlNode = defaultSqlNode;
  }

  @Override
  public boolean apply(DynamicContext context) {
    // when 節(jié)點(diǎn)根據(jù) test 表達(dá)式判斷是否生效
    for (SqlNode sqlNode : ifSqlNodes) {
      if (sqlNode.apply(context)) {
        return true;
      }
    }
    // when 節(jié)點(diǎn)如果都未生效先壕,且存在 otherwise 節(jié)點(diǎn),則使用 otherwise 節(jié)點(diǎn)
    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標(biāo)簽
 * @author Clinton Begin
 */
public class ForEachSqlNode implements SqlNode {
  /**
   * foreach的
   */
  public static final String ITEM_PREFIX = "__frch_";

  private final ExpressionEvaluator evaluator;
  /**
   *  foreach標(biāo)簽的collection屬性谆甜,循環(huán)的集合對(duì)象名
   */
  private final String collectionExpression;
  /**
   *  forEach標(biāo)簽下的子節(jié)點(diǎn)垃僚,封裝成MixedSqlNode
   */
  private final SqlNode contents;
  /**
   * foreach標(biāo)簽的open屬性,foreach代碼的開(kāi)始符號(hào)规辱,一般是(和close=")"合用谆棺。常用在in(),values()時(shí)。該參數(shù)可選罕袋。
   */
  private final String open;
  /**
   * foreach標(biāo)簽的close屬性,foreach代碼的關(guān)閉符號(hào)改淑,一般是)和open="("合用。常用在in(),values()時(shí)浴讯。該參數(shù)可選朵夏。
   */
  private final String close;
  /**
   * foreach標(biāo)簽的separator屬性,元素之間的分隔符,例如在in()的時(shí)候榆纽,separator=","會(huì)自動(dòng)在元素中間用“,“隔開(kāi)仰猖,避免手動(dòng)輸入逗號(hào)導(dǎo)致sql錯(cuò)誤,如in(1,2,)這樣奈籽。該參數(shù)可選
   */
  private final String separator;
  /**
   * foreach標(biāo)簽的item屬性饥侵,集合對(duì)象的元素名
   */
  private final String item;
  /**
   * foreach標(biāo)簽的index屬性,在list和數(shù)組中,index是元素的序號(hào)衣屏,在map中躏升,index是元素的key,該參數(shù)可選勾拉。
   */
  private final String index;
  /**
   * mybatis全局配置信息
   */
  private final Configuration configuration;

  /**
   *
   * @param configuration mybatis全局配置信息
   * @param contents forEach標(biāo)簽下的子節(jié)點(diǎn)煮甥,封裝成MixedSqlNode
   * @param collectionExpression foreach標(biāo)簽的collection屬性盗温,循環(huán)的集合對(duì)象名
   * @param index foreach標(biāo)簽的index屬性藕赞,在list和數(shù)組中,index是元素的序號(hào),在map中卖局,index是元素的key斧蜕,該參數(shù)可選。
   * @param item foreach標(biāo)簽的item屬性砚偶,集合對(duì)象的元素名
   * @param open foreach標(biāo)簽的open屬性批销,foreach代碼的開(kāi)始符號(hào)洒闸,一般是(和close=")"合用。常用在in(),values()時(shí)均芽。該參數(shù)可選丘逸。
   * @param close foreach標(biāo)簽的close屬性,foreach代碼的關(guān)閉符號(hào),一般是)和open="("合用掀宋。常用在in(),values()時(shí)深纲。該參數(shù)可選。
   * @param separator foreach標(biāo)簽的separator屬性,元素之間的分隔符劲妙,例如在in()的時(shí)候湃鹊,separator=","會(huì)自動(dòng)在元素中間用“,“隔開(kāi),避免手動(dòng)輸入逗號(hào)導(dǎo)致sql錯(cuò)誤镣奋,如in(1,2,)這樣币呵。該參數(shù)可選
   */
  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) {
    //獲取參數(shù)對(duì)象
    Map<String, Object> bindings = context.getBindings();
    //獲取expression對(duì)應(yīng)在parameterObject的對(duì)象的Iterable對(duì)象
    final Iterable<?> iterable = evaluator.evaluateIterable(collectionExpression, bindings);
    //沒(méi)有元素就直接返回true
    if (!iterable.iterator().hasNext()) {
      return true;
    }
    boolean first = true;
    //將open添加到context中
    applyOpen(context);
    // 迭代索引
    int i = 0;
    for (Object o : iterable) {
      DynamicContext oldContext = context;
      //對(duì)content進(jìn)行裝飾成prefixedContext
      if (first || separator == null) {
        context = new PrefixedContext(context, "");//首個(gè)元素不需要前綴,所以prefix為空字符串
      } else {
        context = new PrefixedContext(context, separator);
      }
      int uniqueNumber = context.getUniqueNumber();
      // Issue #709
      if (o instanceof Map.Entry) {
        // entry 集合項(xiàng)索引為 key侨颈,集合項(xiàng)為 value
        @SuppressWarnings("unchecked")
        Map.Entry<Object, Object> mapEntry = (Map.Entry<Object, Object>) o;
        applyIndex(context, mapEntry.getKey(), uniqueNumber);
        applyItem(context, mapEntry.getValue(), uniqueNumber);
      } else {
        // 綁定集合項(xiàng)索引關(guān)系
        applyIndex(context, i, uniqueNumber);
        // 綁定集合項(xiàng)關(guān)系
        applyItem(context, o, uniqueNumber);
      }
      // 對(duì)解析的表達(dá)式進(jìn)行替換余赢,如 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) {
        //只要沒(méi)有應(yīng)用前綴,first就一直為true哈垢,正常情況下没佑,這里到這一步,isPrefixApplied一般為false温赔。
        first = !((PrefixedContext) context).isPrefixApplied();
      }
      context = oldContext;
      i++;
    }
    //將close添加到context中
    applyClose(context);
    //移除item綁定關(guān)系
    context.getBindings().remove(item);
    //移除index綁定關(guān)系
    context.getBindings().remove(index);
    return true;
  }

  /**
   * 綁定集合項(xiàng)索引關(guān)系
   * @param context PrefixedContext實(shí)例
   * @param o index值
   * @param i {@code context} 的唯一數(shù)字 context.getUniqueNumber()
   */
  private void applyIndex(DynamicContext context, Object o, int i) {
    if (index != null) {
      context.bind(index, o);
      context.bind(itemizeItem(index, i), o);
    }
  }

  /**
   * 綁定集合項(xiàng)關(guān)系
   * @param context PrefixedContext實(shí)例
   * @param o index值
   * @param i {@code context} 的唯一數(shù)字 context.getUniqueNumber()
   */
  private void applyItem(DynamicContext context, Object o, int i) {
    if (item != null) {
      context.bind(item, o);
      context.bind(itemizeItem(item, i), o);
    }
  }

  /**
   * 應(yīng)用 {@link #open}
   * @param context 用于記錄動(dòng)態(tài)SQL語(yǔ)句解析結(jié)果的容器
   */
  private void applyOpen(DynamicContext context) {
    if (open != null) {
      context.appendSql(open);
    }
  }

  /**
   * 應(yīng)用 {@link #close}
   * @param context  用于記錄動(dòng)態(tài)SQL語(yǔ)句解析結(jié)果的容器
   */
  private void applyClose(DynamicContext context) {
    if (close != null) {
      context.appendSql(close);
    }
  }

  /**
   * 組裝列表項(xiàng)名
   * @param item 項(xiàng)名
   * @param i 位置
   * @return {@link #ITEM_PREFIX} + item + "_" + i
   */
  private static String itemizeItem(String item, int i) {
    return ITEM_PREFIX + item + "_" + i;
  }

  /**
   * 對(duì)解析的表達(dá)式進(jìn)行替換的DynamincContext裝飾類
   */
  private static class FilteredDynamicContext extends DynamicContext {
    /**
     * 委托類對(duì)象
     */
    private final DynamicContext delegate;
    /**
     * {@link #delegate} 的 uniqueNumber
     */
    private final int index;
    /**
     * foreach標(biāo)簽的index屬性蛤奢,在list和數(shù)組中,index是元素的序號(hào),
     * 在map中陶贼,index是元素的key啤贩,該參數(shù)可選。
     */
    private final String itemIndex;
    /**
     * foreach標(biāo)簽的item屬性拜秧,集合對(duì)象的元素名
     */
    private final String item;

    /**
     *
     * @param configuration mybatis全局配置信息
     * @param delegate 委托類對(duì)象
     * @param itemIndex foreach標(biāo)簽的index屬性痹屹,在list和數(shù)組中,index是元素的序號(hào),在map中枉氮,index是元素的key志衍,該參數(shù)可選。
     * @param item foreach標(biāo)簽的item屬性聊替,集合對(duì)象的元素名
     * @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<String, Object> 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 -> {
        // 對(duì)解析的表達(dá)式進(jìn)行替換楼肪,如 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的記錄動(dòng)態(tài)SQL語(yǔ)句解析結(jié)果的容器
   */
  private class PrefixedContext extends DynamicContext {
    /**
     * 委托對(duì)象
     */
    private final DynamicContext delegate;
    /**
     * 前綴
     */
    private final String prefix;
    /**
     * 已經(jīng)應(yīng)用了前綴的標(biāo)記
     */
    private boolean prefixApplied;

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

    /**
     * 是否已經(jīng)應(yīng)用了前綴的標(biāo)記
     * @return
     */
    public boolean isPrefixApplied() {
      return prefixApplied;
    }

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

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

    @Override
    public void appendSql(String sql) {
      //如果還沒(méi)有添加prefix,且sql經(jīng)過(guò)修剪后長(zhǎng)度不為0惹悄,就會(huì)先添加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標(biāo)簽春叫,動(dòng)態(tài) SQL 通常要做的事情是有條件地包含 where 子句的一部分
 * @author Clinton Begin
 */
public class IfSqlNode implements SqlNode {

  /**
   * OGNL 表達(dá)式計(jì)算工具
   */
  private final ExpressionEvaluator evaluator;
  /**
   * ognl表達(dá)式
   */
  private final String test;
  /**
   * if標(biāo)簽下的字節(jié)點(diǎn),封裝成MixedSqlNode
   */
  private final SqlNode contents;

  /**
   *
   * @param contents if標(biāo)簽下的字節(jié)點(diǎn),封裝成MixedSqlNode
   * @param test  ognl表達(dá)式
   */
  public IfSqlNode(SqlNode contents, String test) {
    this.test = test;
    this.contents = contents;
    this.evaluator = new ExpressionEvaluator();
  }

  @Override
  public boolean apply(DynamicContext context) {
    // 根據(jù) test 表達(dá)式判斷當(dāng)前節(jié)點(diǎn)是否生效
    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節(jié)點(diǎn)暂殖,里面存儲(chǔ)著sqlNode結(jié)合价匠,當(dāng)調(diào)用apply方法時(shí),會(huì)遍歷其sqlNode集合的元素呛每,調(diào)用元素的apply進(jìn)行處理踩窖。
 * @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) {
    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標(biāo)簽
 * @author Clinton Begin
 */
public class TrimSqlNode implements SqlNode {

  /**
   * trim標(biāo)簽下的字節(jié)點(diǎn),封裝成MixedSqlNode
   */
  private final SqlNode contents;
  /**
   * trim標(biāo)簽的prefix屬性晨横,給sql語(yǔ)句拼接的前綴
   */
  private final String prefix;
  /**
   * trim標(biāo)簽的suffix屬性毙石,給sql語(yǔ)句拼接的后綴
   */
  private final String suffix;
  /**
   * trim標(biāo)簽的prefixOverrides屬性
   * <p>
   *     去除sql語(yǔ)句前面的關(guān)鍵字或者字符,該關(guān)鍵字或者字符由prefixOverrides屬性指定颓遏,
   *     假設(shè)該屬性指定為"AND"徐矩,當(dāng)sql語(yǔ)句的開(kāi)頭為"AND",trim標(biāo)簽將會(huì)去除該"AND"
   * </p>
   */
  private final List<String> prefixesToOverride;
  /**
   * trim標(biāo)簽的suffixOverride,去除sql語(yǔ)句后面的關(guān)鍵字或者字符叁幢,該關(guān)鍵字或者字符由suffixOverrides屬性指定
   */
  private final List<String> suffixesToOverride;
  /**
   * mybatis的全局配置信息
   */
  private final Configuration configuration;

  /**
   *
   * @param configuration mybatis的全局配置信息
   * @param contents trim標(biāo)簽下的字節(jié)點(diǎn)滤灯,封裝成MixedSqlNode
   * @param prefix trim標(biāo)簽的prefix屬性
   * @param prefixesToOverride trim標(biāo)簽的prefixOverrides屬性
   * @param suffix trim標(biāo)簽的suffix屬性
   * @param suffixesToOverride  trim標(biāo)簽的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標(biāo)簽下的字節(jié)點(diǎn),封裝成MixedSqlNode
   * @param prefix trim標(biāo)簽的prefix屬性
   * @param prefixesToOverride trim標(biāo)簽的prefixOverrides屬性
   * @param suffix trim標(biāo)簽的suffix屬性
   * @param suffixesToOverride trim標(biāo)簽的suffixOverride
   */
  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為DynamincContext的代理類曼玩,提供了根據(jù)suffix,prefix,suffixOverride,prefixOverride進(jìn)行
     * 對(duì)filteredDynamicContext修剪的方法鳞骤。
     */
    FilteredDynamicContext filteredDynamicContext = new FilteredDynamicContext(context);
    //將解析出來(lái)的值添加到context中
    boolean result = contents.apply(filteredDynamicContext);
    //根據(jù)suffix,prefix,suffixOverride,prefixOverride進(jìn)行對(duì)filteredDynamicContext修剪
    filteredDynamicContext.applyAll();
    return result;
  }

  /**
   * 將prefixesToOverride屬性或者suffixesToOverride屬性值根據(jù)'|'分割成List&lt;String&gt;,
   * eg:prefixesToOverride='and | or' ==> List{AND,OR}
   * @param overrides prefixesToOverride屬性或者suffixesToOverride屬性
   * @return 返回根據(jù)'|'分割成List&lt;String&gt;,如果overrides為null黍判,會(huì)返回Collections的靜態(tài)空集合對(duì)象
   */
  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()) {
        //將分割處理的token以大寫(xiě)的形式添加到list中
        list.add(parser.nextToken().toUpperCase(Locale.ENGLISH));
      }
      return list;
    }
    //返回Collections的靜態(tài)空集合對(duì)象
    return Collections.emptyList();
  }

  /**
   * DynamincContext的代理類豫尽,提供了根據(jù)suffix,prefix,suffixOverride,prefixOverride進(jìn)行
   *  對(duì)filteredDynamicContext修剪的方法。
   */
  private class FilteredDynamicContext extends DynamicContext {
    /**
     * DynamicContext委托對(duì)象
     */
    private DynamicContext delegate;
    /**
     * 已經(jīng)應(yīng)用了前綴的標(biāo)記
     */
    private boolean prefixApplied;
    /**
     * 已經(jīng)應(yīng)用了后綴的標(biāo)記
     */
    private boolean suffixApplied;
    /**
     * sql腳本
     */
    private StringBuilder sqlBuffer;

    /**
     *
     * @param delegate DynamicContext委托對(duì)象
     */
    public FilteredDynamicContext(DynamicContext delegate) {
      super(configuration, null);
      this.delegate = delegate;
      this.prefixApplied = false;
      this.suffixApplied = false;
      this.sqlBuffer = new StringBuilder();
    }

    /**
     * 對(duì)sqlBuffer根據(jù)suffix,prefix,suffixOverride,prefixOverride進(jìn)行修剪
     */
    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<String, Object> 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();
    }

    /**
     * 應(yīng)用前綴
     * @param sql sql腳本
     * @param trimmedUppercaseSql 去掉了 {@code sql} 的前和尾的所有空格顷帖,以及全部字母轉(zhuǎn)換成了大寫(xiě)的字符串
     */
    private void applyPrefix(StringBuilder sql, String trimmedUppercaseSql) {
      if (!prefixApplied) {
        prefixApplied = true;
        if (prefixesToOverride != null) {
          for (String toRemove : prefixesToOverride) {
            //是否以prefiexesToOverride的元素開(kāi)頭美旧,如果是,就以從0位置開(kāi)始刪除贬墩。只會(huì)執(zhí)行一下刪除
            if (trimmedUppercaseSql.startsWith(toRemove)) {
              sql.delete(0, toRemove.trim().length());
              break;
            }
          }
        }
        //插入prefix到sql的開(kāi)頭
        if (prefix != null) {
          //因?yàn)椴迦氲奈恢檬鞘莝ql的開(kāi)頭榴嗅,所以要先插入空格
          sql.insert(0, " ");
          sql.insert(0, prefix);
        }
      }
    }

    /**
     * 應(yīng)用后綴
     * @param sql sql腳本
     * @param trimmedUppercaseSql 去掉了 {@code sql} 的前和尾的所有空格,以及全部字母轉(zhuǎn)換成了大寫(xiě)的字符串
     */
    private void applySuffix(StringBuilder sql, String trimmedUppercaseSql) {
      if (!suffixApplied) {
        suffixApplied = true;
        if (suffixesToOverride != null) {
          for (String toRemove : suffixesToOverride) {
            //刪除在sql中以suffixesToOverride的元素結(jié)尾的字符串陶舞,該刪除只會(huì)執(zhí)行異常
            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的結(jié)尾
        if (suffix != null) {
          //因?yàn)椴迦氲奈恢檬莝ql的結(jié)尾嗽测,所以要先插入空格
          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 元素會(huì)動(dòng)態(tài)前置 SET 關(guān)鍵字,同時(shí)也會(huì)消除無(wú)關(guān)的逗號(hào)肿孵,
 * 因?yàn)橛昧藯l件語(yǔ)句之后很可能就會(huì)在生成的賦值語(yǔ)句的后面留下這些逗號(hào)唠粥。
 * <p>
 *      實(shí)現(xiàn)邏輯是套用TrimSqlNode
 * </p>
 * @author Clinton Begin
 */
public class SetSqlNode extends TrimSqlNode {

  private static final List<String> COMMA = Collections.singletonList(",");

  /**
   *
   * @param configuration mybatis全局配置信息
   * @param contents set標(biāo)簽下的字節(jié)點(diǎn),封裝成MixedSqlNode
   */
  public SetSqlNode(Configuration configuration,SqlNode contents) {
    /**
     * 設(shè)置為'SET'為前綴停做,設(shè)置COMMA為trimSqlNode的prefixOverride,到時(shí)候調(diào)用apply
     * 方法就會(huì)自動(dòng)添加'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;

/**
 * 靜態(tài)文本SQL節(jié)點(diǎn),其apply方法使得只是將存儲(chǔ)的文本內(nèi)容直接添加到DynamicContext對(duì)象中
 * @author Clinton Begin
 */
public class StaticTextSqlNode implements SqlNode {
  /**
   * 靜態(tài)文本
   */
  private final String text;

  /**
   *
   * @param text 靜態(tài)文本
   */
  public StaticTextSqlNode(String text) {
    this.text = text;
  }

  /**
   * 將{@link #text} 直接添加到 {@code context}
   * @param context 一個(gè)用于記錄動(dòng)態(tài)SQL語(yǔ)句解析結(jié)果的容器
   * @return 寫(xiě)死返回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節(jié)點(diǎn)雅宾。用傳入的實(shí)際參數(shù)對(duì)象中的屬性值對(duì)${}進(jìn)行直接替換养涮,并且不會(huì)進(jìn)行任何檢查!
 * @author Clinton Begin
 */
public class TextSqlNode implements SqlNode {
  /**
   * 文本內(nèi)容
   */
  private final String text;
  /**
   * 需要匹配的正則表達(dá)式
   */
  private final Pattern injectionFilter;

  /**
   *
   * @param text 文本內(nèi)容
   */
  public TextSqlNode(String text) {
    this(text, null);
  }

  /**
   *
   * @param text 文本內(nèi)容
   * @param injectionFilter 需要匹配的正則表達(dá)式
   */
  public TextSqlNode(String text, Pattern injectionFilter) {
    this.text = text;
    this.injectionFilter = injectionFilter;
  }

  /**
   * 是否動(dòng)態(tài)SQL眉抬。當(dāng)sql中包含有${}時(shí)贯吓,就認(rèn)為是動(dòng)態(tài)SQL
   */
  public boolean isDynamic() {
    //DynamicCheckerTokenParse:當(dāng)sql中包含有${}時(shí),就認(rèn)為是動(dòng)態(tài)SQL
    DynamicCheckerTokenParser checker = new DynamicCheckerTokenParser();
    GenericTokenParser parser = createParser(checker);
    //GenericTokenParser會(huì)將檢查到的'${XXX}'的XXX傳遞給DynamicCheckerTokenParser的handleToken方法進(jìn)行處理.
    parser.parse(text);
    return checker.isDynamic();
  }

  /**
   * 用傳入的實(shí)際參數(shù)對(duì)象中的屬性值對(duì)${}進(jìn)行直接替換蜀变,并且不會(huì)進(jìn)行任何檢查悄谐!
   * @param context 用于記錄動(dòng)態(tài)SQL語(yǔ)句解析結(jié)果的容器
   * @return 寫(xiě)死了true
   */
  @Override
  public boolean apply(DynamicContext context) {

    GenericTokenParser parser = createParser(new BindingTokenParser(context, injectionFilter));
    /**
     * GenericTokenParser會(huì)將檢查到的'${XXX}'的XXX傳遞給BindingTokenParser的handleToken方法進(jìn)行處理.
     * 將解析處理的結(jié)果添加到context中。
     */
    context.appendSql(parser.parse(text));
    return true;
  }

  /**
   * 創(chuàng)建通用token解析器
   * @param handler token處理器
   * @return 專門處理'${...}'形式的token的通用token解析器
   */
  private GenericTokenParser createParser(TokenHandler handler) {
    return new GenericTokenParser("${", "}", handler);
  }

  /**
   * 用于獲取替換${}的真實(shí)參數(shù)值
   */
  private static class BindingTokenParser implements TokenHandler {

    private DynamicContext context;
    /**
     * 正則表達(dá)式
     */
    private Pattern injectionFilter;

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

    @Override
    public String handleToken(String content) {
      //TODO 沒(méi)有看懂這里干嘛的
      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);
      }
      // 從傳入的參數(shù)中獲取到${}對(duì)應(yīng)的值并對(duì)${}進(jìn)行替換
      Object value = OgnlCache.getValue(content, context.getBindings());
      String srtValue = value == null ? "" : String.valueOf(value); // issue #274 return "" instead of "null"
      //檢查值是否匹配正則表達(dá)式库北,不匹配拋出異常
      checkInjection(srtValue);
      return srtValue;
    }

    /**
     * 檢查值是否匹配正則表達(dá)式爬舰,不匹配拋出異常
     * @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的動(dòng)態(tài)檢測(cè),當(dāng)sql中包含有${}時(shí)就認(rèn)為是動(dòng)態(tài)SQL
   */
  private static class DynamicCheckerTokenParser implements TokenHandler {

    /**
     * 是否動(dòng)態(tài)SQL,當(dāng)sql中包含有${}時(shí)就認(rèn)為是動(dòng)態(tài)SQL
     */
    private boolean isDynamic;

    public DynamicCheckerTokenParser() {
      // Prevent Synthetic Access
    }

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

    /**
     * 當(dāng)GenericTokenParser檢測(cè)到sql中包含有${}時(shí)寒瓦,只是簡(jiǎn)單的標(biāo)記為動(dòng)態(tài)
     */
    @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;
  /**
   * 表達(dá)式
   */
  private final String expression;

  /**
   *
   * @param var 變量名
   * @param exp 表達(dá)式
   */
  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 標(biāo)簽知道只有在一個(gè)以上的if條件有值的情況下才去插入"WHERE"子句情屹。
 * 而且,若最后的內(nèi)容是"AND"或"OR"開(kāi)頭的杂腰,where 元素也知道如何將他們?nèi)コ? * <p>
 *     實(shí)現(xiàn)邏輯是套用TrimSqlNode
 * </p>
 * @author Clinton Begin
 */
public class WhereSqlNode extends TrimSqlNode {

  private static List<String> prefixList = Arrays.asList("AND ","OR ","AND\n", "OR\n", "AND\r", "OR\r", "AND\t", "OR\t");

  /**
   *
   * @param configuration mybatis全局配置信息
   * @param contents where標(biāo)簽下的字節(jié)點(diǎn)垃你,封裝成MixedSqlNode
   */
  public WhereSqlNode(Configuration configuration, SqlNode contents) {
    /**
     * 設(shè)置為'WHERE'為前綴低矮,設(shè)置prefixList為trimSqlNode的prefixOverride,到時(shí)候調(diào)用apply
     * 方法就會(huì)自動(dòng)添加'WHERE'作為前綴赶促,并覆蓋掉'WHERE'后面prefixList的元素
     */
    super(configuration, contents, "WHERE", prefixList, null, null);
  }

}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末懂算,一起剝皮案震驚了整個(gè)濱河市西篓,隨后出現(xiàn)的幾起案子汇竭,更是在濱河造成了極大的恐慌讲婚,老刑警劉巖浪秘,帶你破解...
    沈念sama閱讀 218,284評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件评架,死亡現(xiàn)場(chǎng)離奇詭異漓帅,居然都是意外死亡锨亏,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門忙干,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)屯伞,“玉大人,你說(shuō)我怎么就攤上這事豪直×右。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,614評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵弓乙,是天一觀的道長(zhǎng)末融。 經(jīng)常有香客問(wèn)我,道長(zhǎng)暇韧,這世上最難降的妖魔是什么勾习? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,671評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮懈玻,結(jié)果婚禮上巧婶,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好艺栈,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,699評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布英岭。 她就那樣靜靜地躺著,像睡著了一般湿右。 火紅的嫁衣襯著肌膚如雪诅妹。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,562評(píng)論 1 305
  • 那天毅人,我揣著相機(jī)與錄音吭狡,去河邊找鬼。 笑死丈莺,一個(gè)胖子當(dāng)著我的面吹牛划煮,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播缔俄,決...
    沈念sama閱讀 40,309評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼般此,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了牵现?” 一聲冷哼從身側(cè)響起铐懊,我...
    開(kāi)封第一講書(shū)人閱讀 39,223評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎瞎疼,沒(méi)想到半個(gè)月后科乎,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,668評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡贼急,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,859評(píng)論 3 336
  • 正文 我和宋清朗相戀三年茅茂,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片太抓。...
    茶點(diǎn)故事閱讀 39,981評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡空闲,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出走敌,到底是詐尸還是另有隱情碴倾,我是刑警寧澤,帶...
    沈念sama閱讀 35,705評(píng)論 5 347
  • 正文 年R本政府宣布掉丽,位于F島的核電站跌榔,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏捶障。R本人自食惡果不足惜僧须,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,310評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望项炼。 院中可真熱鬧担平,春花似錦示绊、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,904評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至空另,卻和暖如春盆耽,著一層夾襖步出監(jiān)牢的瞬間蹋砚,已是汗流浹背扼菠。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,023評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留坝咐,地道東北人循榆。 一個(gè)月前我還...
    沈念sama閱讀 48,146評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像墨坚,于是被迫代替她去往敵國(guó)和親秧饮。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,933評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容