日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

Mybatis源码阅读(二):动态节点解析2.1 —— SqlSource和SqlNode

發(fā)布時(shí)間:2025/3/11 编程问答 16 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Mybatis源码阅读(二):动态节点解析2.1 —— SqlSource和SqlNode 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

*************************************優(yōu)雅的分割線 **********************************

分享一波:程序員賺外快-必看的巔峰干貨

如果以上內(nèi)容對(duì)你覺得有用,并想獲取更多的賺錢方式和免費(fèi)的技術(shù)教程

請(qǐng)關(guān)注微信公眾號(hào):HB荷包

一個(gè)能讓你學(xué)習(xí)技術(shù)和賺錢方法的公眾號(hào),持續(xù)更新

前言

前面的文章介紹了mybatis核心配置文件和mapper文件的解析,之后因?yàn)榧影啾容^重,加上個(gè)人也比較懶,一拖就是將近半個(gè)月,今天抽空開始第二部分的閱讀。

由前面的文章可知,mapper文件中定義的Sql節(jié)點(diǎn)會(huì)被解析成MappedStatement,其中的SQL語(yǔ)句會(huì)被解析成SqlSource。而Sql語(yǔ)句中定義的動(dòng)態(tài)sql節(jié)點(diǎn)(如if節(jié)點(diǎn)、foreach節(jié)點(diǎn))會(huì)被解析成SqlNode。SqlNode節(jié)點(diǎn)的解析中會(huì)使用到Ognl表達(dá)式(沒錯(cuò)就是是struts2用的那玩意。本以為隨著struts2和jsp淡出開發(fā)環(huán)境,這種動(dòng)態(tài)標(biāo)簽也會(huì)隨之過時(shí),沒想到mybatis里依然沿用了ognl),這個(gè)內(nèi)容介紹起來有點(diǎn)麻煩,因此感興趣的讀者請(qǐng)自行了解一下。
SqlSource

Sql節(jié)點(diǎn)中的Sql語(yǔ)句會(huì)被解析成SqlSource,SqlSource接口中只定義了一個(gè)方法 getBoundSql 。該方法用于表示解析后的Sql語(yǔ)句(帶問號(hào))。

/**

  • 該接口用于標(biāo)識(shí)映射文件或者注解中定義的sql語(yǔ)句

  • 這里的sql可能帶有#{}等標(biāo)志

  • @author Clinton Begin
    */
    public interface SqlSource {

    /**

    • 可執(zhí)行的sql
    • @param parameterObject
    • @return
      */
      BoundSql getBoundSql(Object parameterObject);
      }

[點(diǎn)擊并拖拽以移動(dòng)]

SqlSource的繼承關(guān)系如下圖所示。每個(gè)實(shí)現(xiàn)類都比較簡(jiǎn)單,下面只做簡(jiǎn)單的說明。

DynamicSqlSource用于處理動(dòng)態(tài)語(yǔ)句(帶有動(dòng)態(tài)sql標(biāo)簽),RawSqlSource用于處理靜態(tài)語(yǔ)句(沒有動(dòng)態(tài)sql標(biāo)簽),二者最終會(huì)解析成StaticSqlSource。StaticSqlSource可能會(huì)帶有問號(hào)。這里暫時(shí)只將代碼簡(jiǎn)單的貼出來,部分內(nèi)容需要結(jié)合后面才可以加注釋(如SqlNode)

/**

  • 處理靜態(tài)sql語(yǔ)句

  • @since 3.2.0

  • @author Eduardo Macarron
    */
    public class RawSqlSource implements SqlSource {

    private final SqlSource sqlSource;

    public RawSqlSource(Configuration configuration, SqlNode rootSqlNode, Class<?> parameterType) {
    this(configuration, getSql(configuration, rootSqlNode), parameterType);
    }

    public RawSqlSource(Configuration configuration, String sql, Class<?> parameterType) {
    SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
    Class<?> clazz = parameterType == null ? Object.class : parameterType;
    sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap<>());
    }

    private static String getSql(Configuration configuration, SqlNode rootSqlNode) {
    DynamicContext context = new DynamicContext(configuration, null);
    rootSqlNode.apply(context);
    return context.getSql();
    }

    @Override
    public BoundSql getBoundSql(Object parameterObject) {
    return sqlSource.getBoundSql(parameterObject);
    }
    }

/**

  • 負(fù)責(zé)解析動(dòng)態(tài)sql語(yǔ)句

  • 包含#{}占位符

  • @author Clinton Begin
    */
    public class DynamicSqlSource implements SqlSource {

    private final Configuration configuration;
    private final SqlNode rootSqlNode;

    public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) {
    this.configuration = configuration;
    this.rootSqlNode = rootSqlNode;
    }

    @Override
    public BoundSql getBoundSql(Object parameterObject) {
    DynamicContext context = new DynamicContext(configuration, parameterObject);
    rootSqlNode.apply(context);
    SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
    Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
    SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
    context.getBindings().forEach(boundSql::setAdditionalParameter);
    return boundSql;
    }

}

/**

  • 經(jīng)過DynamicSqlSource和RawSqlSource處理后

  • 這里存放的sql可能含有?占位符

  • @author Clinton Begin
    */
    public class StaticSqlSource implements SqlSource {

    private final String sql;
    private final List parameterMappings;
    private final Configuration configuration;

    public StaticSqlSource(Configuration configuration, String sql) {
    this(configuration, sql, null);
    }

    public StaticSqlSource(Configuration configuration, String sql, List parameterMappings) {
    this.sql = sql;
    this.parameterMappings = parameterMappings;
    this.configuration = configuration;
    }

    @Override
    public BoundSql getBoundSql(Object parameterObject) {
    return new BoundSql(configuration, sql, parameterMappings, parameterObject);
    }

}

ProviderSqlSource暫時(shí)不貼出來(還沒讀到這里)
DynamicContext

DynamicContext用于記錄解析動(dòng)態(tài)Sql時(shí)產(chǎn)生的Sql片段。這里也先將主要代碼放出來。

/**

  • 用于記錄解析動(dòng)態(tài)SQL語(yǔ)句之后產(chǎn)生的SQL語(yǔ)句片段

  • 可以認(rèn)為它是一個(gè)用于記錄動(dòng)態(tài)SQL語(yǔ)句解析生產(chǎn)的容器

  • @author Clinton Begin
    */
    public class DynamicContext {

    public static final String PARAMETER_OBJECT_KEY = “_parameter”;
    public static final String DATABASE_ID_KEY = “_databaseId”;

    static {
    OgnlRuntime.setPropertyAccessor(ContextMap.class, new ContextAccessor());
    }

    /**

    • 參數(shù)上下文
      /
      private final ContextMap bindings;
      /*
    • 在SQL弄得解析動(dòng)態(tài)SQL時(shí),會(huì)將解析后的SQL語(yǔ)句片段添加到該屬性總保存
    • 最終拼湊出一條完整的SQL
      */
      private final StringJoiner sqlBuilder = new StringJoiner(" ");
      private int uniqueNumber = 0;

    /**

    • 構(gòu)造中初始化bindings集合
    • @param configuration
    • @param parameterObject 運(yùn)行時(shí)用戶傳入的參數(shù)。
      */
      public DynamicContext(Configuration configuration, Object parameterObject) {
      if (parameterObject != null && !(parameterObject instanceof Map)) {
      // 非Map就去找對(duì)應(yīng)的類型處理器
      MetaObject metaObject = configuration.newMetaObject(parameterObject);
      boolean existsTypeHandler = configuration.getTypeHandlerRegistry().hasTypeHandler(parameterObject.getClass());
      bindings = new ContextMap(metaObject, existsTypeHandler);
      } else {
      bindings = new ContextMap(null, false);
      }
      bindings.put(PARAMETER_OBJECT_KEY, parameterObject);
      bindings.put(DATABASE_ID_KEY, configuration.getDatabaseId());
      }

    public Map<String, Object> getBindings() {
    return bindings;
    }

    public void bind(String name, Object value) {
    bindings.put(name, value);
    }

    /**

    • 追加SQL片段
    • @param sql
      */
      public void appendSql(String sql) {
      sqlBuilder.add(sql);
      }

    /**

    • 獲取解析后的SQL語(yǔ)句
    • @return
      */
      public String getSql() {
      return sqlBuilder.toString().trim();
      }

    public int getUniqueNumber() {
    return uniqueNumber++;
    }

}

SqlNode

SqlNode表示Sql節(jié)點(diǎn)中的動(dòng)態(tài)Sql。該類(接口)只有一個(gè)apply方法,用于解析動(dòng)態(tài)Sql節(jié)點(diǎn),并調(diào)用DynamicContext的appendSql方法去拼接sql語(yǔ)句。

/**

  • @author Clinton Begin
    */
    public interface SqlNode {

    /**

    • 根據(jù)用戶傳入的實(shí)參去解析動(dòng)態(tài)SQL節(jié)點(diǎn)
    • 并調(diào)用DynamicContext.appendSql將解析后的SQL片段
    • 追加到DynamicContext.sqlBuilder保存
    • @param context
    • @return
      */
      boolean apply(DynamicContext context);
      }

SqlNode實(shí)現(xiàn)類很多,如圖所示。光看實(shí)現(xiàn)類的名稱,想必大家都可以猜出這些實(shí)現(xiàn)類的作用了。下面將對(duì)這些實(shí)現(xiàn)類一一解釋

StaticTextSqlNode使用text字段記錄非動(dòng)態(tài)Sql節(jié)點(diǎn),apply方法直接將text字段追加到DynamicContext.sqlBuilder;MixedSqlNode中使用contents字段存放子節(jié)點(diǎn)的動(dòng)態(tài)sql,apply方法則是遍歷contents去調(diào)用每個(gè)SqlNode的apply方法,代碼都比較簡(jiǎn)單就不貼出來了。
TextSqlNode

TextSqlNode表示包含的sql節(jié)點(diǎn),isDynamic方法用于檢測(cè)sql中是否包含{}的sql節(jié)點(diǎn),isDynamic方法用于檢測(cè)sql中是否包含sql節(jié)點(diǎn)isDynamic測(cè)sql{}占位符。該類的apply方法會(huì)使用GenericTokenParser將占位符解析成實(shí)際意義的參數(shù)值,因此{(lán)}占位符解析成實(shí)際意義的參數(shù)值,因此實(shí)數(shù){}在mybatis中會(huì)有注入風(fēng)險(xiǎn),應(yīng)當(dāng)慎用,盡量用于非前端傳遞的參數(shù)。這里比較特殊的場(chǎng)景就是order by。order by后面只能使用${}占位符,因此前端操作排序列時(shí),務(wù)必要做防注入處理。

/**

  • 包含${}的sql

  • @author Clinton Begin
    */
    public class TextSqlNode implements SqlNode {
    private final String text;
    private final Pattern injectionFilter;

    public TextSqlNode(String text) {
    this(text, null);
    }

    public TextSqlNode(String text, Pattern injectionFilter) {
    this.text = text;
    this.injectionFilter = injectionFilter;
    }

    public boolean isDynamic() {
    DynamicCheckerTokenParser checker = new DynamicCheckerTokenParser();
    GenericTokenParser parser = createParser(checker);
    parser.parse(text);
    return checker.isDynamic();
    }

    @Override
    public boolean apply(DynamicContext context) {
    GenericTokenParser parser = createParser(new BindingTokenParser(context, injectionFilter));
    context.appendSql(parser.parse(text));
    return true;
    }

    private GenericTokenParser createParser(TokenHandler handler) {
    // 這里標(biāo)識(shí)解析的是占位符returnnewGenericTokenParser("{}占位符 return new GenericTokenParser("returnnewGenericTokenParser("{", “}”, 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;}@Overridepublic String handleToken(String content) {// 獲取用戶提供的實(shí)參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);}// 通過ognl解析content的值Object value = OgnlCache.getValue(content, context.getBindings());String srtValue = value == null ? "" : String.valueOf(value); // issue #274 return "" instead of "null"checkInjection(srtValue);return srtValue;}private void checkInjection(String value) {if (injectionFilter != null && !injectionFilter.matcher(value).matches()) {throw new ScriptingException("Invalid input. Please conform to regex" + injectionFilter.pattern());}}

    }

    private static class DynamicCheckerTokenParser implements TokenHandler {

    private boolean isDynamic;public DynamicCheckerTokenParser() {// Prevent Synthetic Access}public boolean isDynamic() {return isDynamic;}@Overridepublic String handleToken(String content) {this.isDynamic = true;return null;}

    }

}

IfSqlNode

該類表示mybatis中的if標(biāo)簽。if標(biāo)簽中使用的其實(shí)就是Ognl語(yǔ)句,因此可以有一些很花哨的寫法,如調(diào)用參數(shù)的equals方法等,這里不對(duì)Ognl表達(dá)式做過多的介紹。

/**

  • if節(jié)點(diǎn)

  • @author Clinton Begin
    /
    public class IfSqlNode implements SqlNode {
    /*

    • if節(jié)點(diǎn)的test表達(dá)式值
      /
      private final ExpressionEvaluator evaluator;
      /*
    • if節(jié)點(diǎn)的test表達(dá)式
      /
      private final String test;
      /*
    • if節(jié)點(diǎn)的子節(jié)點(diǎn)
      */
      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) {
    // 檢測(cè)表達(dá)式是否為true,來決定是否執(zhí)行apply方法
    if (evaluator.evaluateBoolean(test, context.getBindings())) {
    contents.apply(context);
    return true;
    }
    return false;
    }

}

TrimSqlNode

trimSqlNode用于根據(jù)解析結(jié)果添加或刪除后綴活前綴。

/**

  • 根據(jù)解析結(jié)果添加或刪除后綴或前綴

  • @author Clinton Begin
    */
    public class TrimSqlNode implements SqlNode {

    /**

    • trim節(jié)點(diǎn)的子節(jié)點(diǎn)
      /
      private final SqlNode contents;
      /*
    • 前綴
      /
      private final String prefix;
      /*
    • 后綴
      /
      private final String suffix;
      /*
    • 如果trim節(jié)點(diǎn)包裹的SQL是空語(yǔ)句,刪除指定的前綴,如where
      /
      private final List prefixesToOverride;
      /*
    • 如果trim節(jié)點(diǎn)包裹的SQL是空語(yǔ)句,刪除指定的后綴,如逗號(hào)
      */
      private final List 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 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 filteredDynamicContext = new FilteredDynamicContext(context);
    boolean result = contents.apply(filteredDynamicContext);
    // 處理前綴和后綴
    filteredDynamicContext.applyAll();
    return result;
    }

    /**

    • 對(duì)prefixOverrides和suffixOverride屬性解析
    • 并初始化兩個(gè)Override集合
    • @param overrides
    • @return
      */
      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()) {
      list.add(parser.nextToken().toUpperCase(Locale.ENGLISH));
      }
      return list;
      }
      return Collections.emptyList();
      }

    private class FilteredDynamicContext extends DynamicContext {
    /**
    * 上下文對(duì)象
    /
    private DynamicContext delegate;
    /*
    * 標(biāo)識(shí)已經(jīng)處理過的前綴和后綴
    /
    private boolean prefixApplied;
    private boolean suffixApplied;
    /*
    * 記錄子節(jié)點(diǎn)解析后的結(jié)果
    */
    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() {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());}@Overridepublic Map<String, Object> getBindings() {return delegate.getBindings();}@Overridepublic void bind(String name, Object value) {delegate.bind(name, value);}@Overridepublic int getUniqueNumber() {return delegate.getUniqueNumber();}@Overridepublic void appendSql(String sql) {sqlBuffer.append(sql);}@Overridepublic String getSql() {return delegate.getSql();}/*** 處理前綴** @param sql sql* @param trimmedUppercaseSql 小寫sql*/private void applyPrefix(StringBuilder sql, String trimmedUppercaseSql) {if (!prefixApplied) {prefixApplied = true;if (prefixesToOverride != null) {for (String toRemove : prefixesToOverride) {// 遍歷prefixesToOverride,如果以其中的某項(xiàng)開頭就從SQL語(yǔ)句開頭剔除if (trimmedUppercaseSql.startsWith(toRemove)) {sql.delete(0, toRemove.trim().length());break;}}}if (prefix != null) {sql.insert(0, " ");sql.insert(0, prefix);}}}/*** 處理后綴。* @param sql* @param trimmedUppercaseSql*/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);}}}

    }

}

WhereSqlNode&SetSqlNode

WhereSqlNode和SetSqlNode分別表示where節(jié)點(diǎn)和set節(jié)點(diǎn)。這兩個(gè)類繼承了TrimSqlNode,因此自帶處理前后綴的功能。

WhereSqlNode將and、or兩個(gè)關(guān)鍵字作為需要?jiǎng)h除的前綴。當(dāng)where的第一個(gè)條件以這兩個(gè)開頭時(shí),會(huì)將and或者or刪除。而SetSqlNode則會(huì)刪除前綴或者后綴的嚶文逗號(hào)。這里只貼出WhereSqlNode代碼。

/**

  • where節(jié)點(diǎn)。繼承了TrimSqlNode

  • 因此where節(jié)點(diǎn)自帶處理前綴后綴功能

  • @author Clinton Begin
    */
    public class WhereSqlNode extends TrimSqlNode {

    /**

    • 設(shè)置前綴是OR和AND,因此解析后的SQL如果以這倆開頭就會(huì)刪掉前綴
      */
      private static List prefixList = Arrays.asList("AND ", "OR ", “AND\n”, “OR\n”, “AND\r”, “OR\r”, “AND\t”, “OR\t”);

    public WhereSqlNode(Configuration configuration, SqlNode contents) {
    super(configuration, contents, “WHERE”, prefixList, null, null);
    }

}

ForeachSqlNode

在動(dòng)態(tài)Sql語(yǔ)句中構(gòu)建in條件時(shí),往往需要遍歷一個(gè)集合,因此使用foreach標(biāo)簽。這里需要著重介紹一下FilteredDynamicContext這個(gè)內(nèi)部類。該類繼承了DynamicContext,用來處理foreach中的#{}占位符。這里是對(duì)其不完全的處理。如#{item}會(huì)被處理乘#{__frch_item_index值}這種格式,用來表示遍歷中的每一項(xiàng)。

/**

  • forEach節(jié)點(diǎn)

  • @author Clinton Begin
    */
    public class ForEachSqlNode implements SqlNode {
    public static final String ITEM_PREFIX = “_frch”;

    /**

    • 判斷循環(huán)終止的條件
      /
      private final ExpressionEvaluator evaluator;
      /*
    • 迭代的集合表達(dá)式
      /
      private final String collectionExpression;
      /*
    • 該節(jié)點(diǎn)下的節(jié)點(diǎn)
      /
      private final SqlNode contents;
      /*
    • 循環(huán)前以什么開頭
      /
      private final String open;
      /*
    • 循環(huán)后以什么結(jié)束
      /
      private final String close;
      /*
    • 循環(huán)過程中的分隔符
      /
      private final String separator;
      /*
    • 每次循環(huán)的變量名
      /
      private final String item;
      /*
    • 當(dāng)前迭代次數(shù)
      */
      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();
    final Iterable<?> iterable = evaluator.evaluateIterable(collectionExpression, bindings);
    if (!iterable.iterator().hasNext()) {
    return true;
    }
    boolean first = true;
    // 循環(huán)之前添加open指定的字符串
    applyOpen(context);
    int i = 0;
    for (Object o : iterable) {
    DynamicContext oldContext = context;
    if (first || separator == null) {
    // 是第一個(gè)循環(huán),并且沒有間隔符
    context = new PrefixedContext(context, “”);
    } else {
    context = new PrefixedContext(context, separator);
    }
    int uniqueNumber = context.getUniqueNumber();
    // 將index和item添加到DynamicContext.bindings集合
    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);
    }
    // 調(diào)用子節(jié)點(diǎn)的apply急需處理
    contents.apply(new FilteredDynamicContext(configuration, context, index, item, uniqueNumber));
    if (first) {
    first = !((PrefixedContext) context).isPrefixApplied();
    }
    context = oldContext;
    i++;
    }
    // 拼接close
    applyClose(context);
    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() {
      return delegate.getBindings();
      }

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

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

      /**

      • 這里會(huì)將#{item}占位符解析成#{__frch_item_index值}

      • @param sql
        /
        @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() {
      return delegate.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() {return prefixApplied;}@Overridepublic Map<String, Object> getBindings() {return delegate.getBindings();}@Overridepublic void bind(String name, Object value) {delegate.bind(name, value);}@Overridepublic void appendSql(String sql) {if (!prefixApplied && sql != null && sql.trim().length() > 0) {delegate.appendSql(prefix);prefixApplied = true;}delegate.appendSql(sql);}@Overridepublic String getSql() {return delegate.getSql();}@Overridepublic int getUniqueNumber() {return delegate.getUniqueNumber();}

    }

}

剩余的如ChooseSqlNode請(qǐng)讀者自行閱讀,代碼也都比較容易理解。
結(jié)語(yǔ)

本次文章只是介紹一下動(dòng)態(tài)sql解析時(shí)常用的類和接口,之后的文章對(duì)動(dòng)態(tài)sql進(jìn)行介紹時(shí)將不再對(duì)這些類進(jìn)行贅述。

最后說一些閑話。

其實(shí)堅(jiān)持寫博客是一件很難的事情。七月份入職以來,便開始考慮寫博客的事,起初不知道從哪寫起,博客質(zhì)量并不高。后來慢慢愛上了閱讀源碼這件事。其實(shí)mybatis源碼我已經(jīng)參照某本書讀完了,但是閱讀完之后我并沒有覺得有何收獲和見解,對(duì)源碼的理解也比較淺顯,因此便想著通過撰寫博客的方式去加深對(duì)源碼的認(rèn)知。Mybatis插件機(jī)制是很重要的特性,而想編寫一個(gè)好的插件就需要對(duì)源碼有深刻的理解,因此源碼不得不讀,對(duì)于一個(gè)java程序員來說這也是必修課。在這幾篇博客的撰寫下,我慢慢養(yǎng)成了寫博客的習(xí)慣,也知道什么該寫,什么不該寫。博客中大部分的內(nèi)容其實(shí)都在代碼注釋上,因此顯得博客內(nèi)容不多,需要閱讀者仔細(xì)閱讀代碼注釋(但愿我的博客有人看吧。)。養(yǎng)成一個(gè)習(xí)慣不容易,這段時(shí)間劃水的過程中對(duì)撰寫博客這件事也有所懈怠(說實(shí)話差點(diǎn)都忘了我還開了這么大一個(gè)坑。)

*************************************優(yōu)雅的分割線 **********************************

分享一波:程序員賺外快-必看的巔峰干貨

如果以上內(nèi)容對(duì)你覺得有用,并想獲取更多的賺錢方式和免費(fèi)的技術(shù)教程

請(qǐng)關(guān)注微信公眾號(hào):HB荷包

一個(gè)能讓你學(xué)習(xí)技術(shù)和賺錢方法的公眾號(hào),持續(xù)更新

總結(jié)

以上是生活随笔為你收集整理的Mybatis源码阅读(二):动态节点解析2.1 —— SqlSource和SqlNode的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。

主站蜘蛛池模板: 色老头综合网 | 黄色片一区二区三区 | 中文一区二区在线播放 | 精品久久在线观看 | 蜜桃一二三区 | 懂色av一区二区三区四区 | 深夜在线| 亚洲AV无码AV吞精久久中文版 | 日本中文字幕一区二区 | 亚洲国产精品一区二区三区 | 久久久久久久久久久国产精品 | 夜操操| 欧美成人精品激情在线视频 | 国产高清无密码一区二区三区 | av一区二区三区免费观看 | 国产小视频在线播放 | 上原亚衣av一区二区三区 | 国产孕妇孕交大片孕 | 国产又粗又长 | 亚洲自拍av在线 | 免费一级全黄少妇性色生活片 | 国产成人在线精品 | 成人在线观看91 | 成人区人妻精品一区 | 男女视频免费看 | 国产69视频在线观看 | 狠狠地日 | 欧美乱三级 | 久久久综合精品 | 在线免费看av片 | 国产精品自产拍 | 韩国中文三级hd字幕 | 国产日本一区二区三区 | 97青草 | 蜜臀一区 | 亚洲少妇一区二区三区 | www.狠狠操.com | 欧美交换国产一区内射 | 在线观看日韩国产 | 日韩欧美国产一区二区在线观看 | 人人爽人人爽人人片 | 色综合视频在线 | 亚洲精品视频在线观看视频 | 久久人久久| 国产一区免费看 | www.精品视频 | 99综合在线 | 国产 日韩 欧美 综合 | 日本精品在线播放 | 青草伊人网 | 国产日韩成人 | 久草手机在线视频 | 精品一区欧美 | 激情自拍视频 | 国产精品一区二区网站 | 少妇精品无码一区二区免费视频 | 日韩美女性生活 | 国产又大又黄视频 | 国产4区| 91人人爱 | 中文字幕欲求不满 | 影音先锋在线播放 | 日韩专区一区二区三区 | 精品国产综合区久久久久久 | www.亚洲成人 | 免费亚洲婷婷 | 麻豆视频播放 | 亚洲男女视频 | 看了下面会湿的视频 | www.国产色 | 永久免费汤不热视频 | 国产亚洲制服欧洲高清一区 | 久久无码专区国产精品s | 日本黄网站色大片免费观看 | 免费在线看污视频 | 神马久久午夜 | 日韩中文字幕视频 | 韩国三级中文字幕hd浴缸戏 | 宅男av在线| 一本久久综合亚洲鲁鲁五月天 | 91麻豆精品国产91久久久更新时间 | 穿情趣内衣被c到高潮视频 欧美性猛交xxxx黑人猛交 | 精品电影一区二区 | 日本人jizz| 绯色av一区二区三区高清 | 国产精品视频播放 | 在线不欧美 | 亚洲精品77777| 精品国产一区一区二区三亚瑟 | 欧美在线免费观看视频 | 四虎av在线| 澳门黄色一级片 | 中文字幕有码在线视频 | 四虎tv| 中文字幕久久熟女蜜桃 | 日本wwwwwww | 精品亚洲一区二区 | 性xxxxxxxxx| 国产精品综合久久久 |