最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

MyBatis?SqlSource源碼示例解析

 更新時(shí)間:2023年02月13日 09:35:21   作者:念念清晰  
這篇文章主要為大家介紹了MyBatis?SqlSource源碼示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

正文

MyBatis版本:3.5.12。

本篇講從mybatis的角度分析SqlSource。在xml中sql可能是帶?的預(yù)處理語(yǔ)句,也可能是帶$或者動(dòng)態(tài)標(biāo)簽的動(dòng)態(tài)語(yǔ)句,也可能是這兩者的混合語(yǔ)句。

SqlSource設(shè)計(jì)的目標(biāo)就是封裝xml的crud節(jié)點(diǎn),使得mybatis運(yùn)行過(guò)程中可以直接通過(guò)SqlSource獲取xml節(jié)點(diǎn)中解析后的SQL。

簡(jiǎn)單的示意圖就是

接下來(lái)我們先來(lái)介紹幾個(gè)基礎(chǔ)的組件,正是這些組件構(gòu)成的SqlSource

SqlNode

mybatis提供了這么9種動(dòng)態(tài)節(jié)點(diǎn):

  • trim
  • where
  • set
  • foreach
  • if
  • choose
  • when
  • otherwise
  • bind

每一種節(jié)點(diǎn)是一個(gè)SqlNode,并且每個(gè)動(dòng)態(tài)節(jié)點(diǎn)都分別對(duì)應(yīng)了一個(gè)XxxSqlNode的實(shí)現(xiàn)類。SqlNode是一個(gè)接口,該接口就代表mybatis的動(dòng)態(tài)節(jié)點(diǎn)。

接下來(lái)我們來(lái)用一個(gè)案例分析mybatis是如何把一個(gè)<select>節(jié)點(diǎn)解析為一個(gè)SqlNode對(duì)象的(update/insert/delete原理一樣)。示例如下

<select id="selectById">
    select * from user
    <where>
        <if test="id != null">
            and id = #{id}
        </if>
        <if test="age != null">
            and age > ${age}
        </if>
    </where>
</select>

它會(huì)被解析成如下這樣一顆SqlNode樹

樹的根節(jié)點(diǎn)都是MixedSqlNode,MixedSqlNode類其中有一個(gè)屬性private final List<SqlNode> contents;專門存放標(biāo)簽下所有的子節(jié)點(diǎn)解析成的SqlNode

該標(biāo)簽的的第一部分就是select * from user;這段文本既不包含標(biāo)簽,也不包含$等表達(dá)式,它就屬于靜態(tài)文本,會(huì)被解析成StaticTextSqlNode

  • 然后與接下來(lái)是一個(gè)wehre標(biāo)簽,它會(huì)被解析為WhereSqlNode
  • whhre標(biāo)簽中有兩個(gè)if標(biāo)簽,這兩個(gè)if標(biāo)簽會(huì)被解析為兩個(gè)IfSqlNode加入到WhereSqlNode
  • 第一個(gè)if標(biāo)簽中的文本不包含$會(huì)被解析成StaticTextSqlNode(沒(méi)錯(cuò),即使它有#符,它不屬于靜態(tài)文本哦。只有包含$才算動(dòng)態(tài)節(jié)點(diǎn))
  • 而第二個(gè)if標(biāo)簽中的文本包含$會(huì)被解析成TextSqlNode

看明白了xml文件中一個(gè)標(biāo)簽是如何由這些SqlNode是組成的。接下來(lái)我們嘮一嘮SqlNode接口的定義

SqlNode接口定義

public interface SqlNode {
  boolean apply(DynamicContext context);
}

SqlNode接口定義非常簡(jiǎn)單,只有一個(gè)apply方法,方法的參數(shù)是DynamicContext,DynamicContext可以看作是一個(gè)sql上下文,它其中維護(hù)了一個(gè)StringBuilder sql字段。這個(gè)字段就是用來(lái)記錄整個(gè)<select>節(jié)點(diǎn)解析過(guò)后的SQL語(yǔ)句的。

mybatis會(huì)在解析過(guò)程中把select標(biāo)簽解析為如上分析的一棵樹MixedSqlNode然后就會(huì)遞歸遍歷這些SqlNode并調(diào)用他們的apply方法,調(diào)用apply方法實(shí)際上就是把標(biāo)簽解析后的sql片段拼接到了context中的sql字段。最后只需要調(diào)用context.getSql方法就可以獲得可執(zhí)行SQL了。而一切都從根節(jié)點(diǎn)的apply方法說(shuō)起,MixedSqlNode的源碼如下

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;
  }
}

可以發(fā)現(xiàn)MixedSqlNode中有一個(gè)List字段,該字段存儲(chǔ)的是樹的葉子節(jié)點(diǎn),在這個(gè)示例中,List字段中應(yīng)該由兩個(gè)SqlNode

  • 第一個(gè)是標(biāo)識(shí)靜態(tài)文本的StaticTextSqlNode,它其中封裝的select * from user文本。
  • 第二個(gè)SqlNode是WhereSqlNode它其中封裝的文本是
  <where>
      <if test="id != null">
          and id = #{id}
      </if>
      <if test="age != null">
          and age > ${age}
      </if>
  </where>

WhereSqlNode類中也還有一個(gè)List屬性,封裝了兩個(gè)if節(jié)點(diǎn),這里就不展開說(shuō)了,我們只需要知道,所有的SqlNode都會(huì)遞歸執(zhí)行apply方法,而apply方法只做了一件事——那就是把SqlNode節(jié)點(diǎn)中的文本經(jīng)過(guò)一系列規(guī)則解析過(guò)后(通常就是刪除標(biāo)簽,刪除無(wú)用的and|or,刪除無(wú)用的,等),返回可執(zhí)行SQL的片段,這些SQL片段最終都會(huì)以如下方法把sql片段拼接,

context.appendSql(text);

最終形成一個(gè)完整的SQL:select * from user where id = 1 (age條件沒(méi)成立)

BoundSql

知道了什么是SqlNode之后,我們?cè)賮?lái)看BoundSqlBoundSql內(nèi)部封裝了可執(zhí)行SQL,先來(lái)看下BoundSql的重要字段

public class BoundSql {
  private final String sql;
  private final List<ParameterMapping> parameterMappings;
  private final Object parameterObject;
  private final Map<String, Object> additionalParameters;
  private final MetaObject metaParameters;
}
  • sql:上小節(jié)說(shuō)到的SqlNode調(diào)用完apply方法后存儲(chǔ)在DynamicContext中的sql就會(huì)被賦值給該字段。sql字段其實(shí)就是類似于select * from user where id = ? 這樣的字符串,
  • parameterObject:用戶傳入的屬性,用于給sql字段的?賦值
  • additionalParameters: bind標(biāo)簽中綁定的值會(huì)存儲(chǔ)在此
  • metaParameters:additionalParameters的元類型

還記得開篇我們說(shuō)的目標(biāo)嗎?我貼過(guò)來(lái)再看一遍

SqlSource設(shè)計(jì)的目標(biāo)就是封裝xml的crud節(jié)點(diǎn),使得mybatis運(yùn)行過(guò)程中可以直接通過(guò)SqlSource獲取xml節(jié)點(diǎn)中解析后的SQL。

簡(jiǎn)單的示意圖就是

那么有了BoundSql,實(shí)現(xiàn)這個(gè)目標(biāo)是不是就很容易了。我們只需要獲取BoundSql對(duì)象,然后再調(diào)用BoundSql#getSql方法就能獲取到可執(zhí)行Sql了。

SqlSource

為了完成開篇說(shuō)的SqlSource的目標(biāo),我們現(xiàn)在迫切想要做的就是獲取BoundSql對(duì)象。剛好SqlSource接口的定義如下

public interface SqlSource {
  BoundSql getBoundSql(Object parameterObject);
}

SqlSource是一個(gè)接口,其中只提供了一個(gè)方法 getBoundSql 。該方法只有一個(gè)參數(shù)Object parameterObject,這個(gè)參數(shù)就是用戶傳入的查詢參數(shù)。SqlSource的繼承體系如下

  • DynamicSqlSource:動(dòng)態(tài)SQL節(jié)點(diǎn)會(huì)被解析為該對(duì)象,那怎么判斷xml文件中的節(jié)點(diǎn)是否是動(dòng)態(tài)的呢?滿足如下兩個(gè)條件的任何一個(gè)就算是動(dòng)態(tài)節(jié)點(diǎn)。一是包含$占位符的表達(dá)式,比如select * from user where id = ${id}。二是包含9種動(dòng)態(tài)標(biāo)簽中的任何一個(gè)(trim set wehre if foreach等9個(gè)。前文有說(shuō))。注意只包含#占位符表達(dá)式的語(yǔ)句不會(huì)被解析成動(dòng)態(tài)標(biāo)簽。
  • ProviderSqlSource:注解定義的SQL
  • RawSqlSource:不是DynamicSqlSource,就會(huì)被解析為RawSqlSource
  • . StaticSqlSource:靜態(tài)文本SQL其中不包含任何$和動(dòng)態(tài)標(biāo)簽。DynamicSqlSource和RawSqlSource最終都會(huì)被解析為StaticSqlSource
  • . VelocitySqlSource:暫且忽略(不在本文討論范圍)

SqlSource解析時(shí)機(jī)

至此SqlSource的組成部分我們都已經(jīng)清楚了,那么XML的節(jié)點(diǎn)在何時(shí)被解析為SqlSource的呢?

答案是在mybatis啟動(dòng)時(shí),會(huì)加載xml文件并進(jìn)行解析。相關(guān)流程如下

  • XMLMapperBuilder#configurationElement,
private void configurationElement(XNode context) {
  try {
    String namespace = context.getStringAttribute("namespace");
    if (namespace == null || namespace.isEmpty()) {
      throw new BuilderException("Mapper's namespace cannot be empty");
    }
    builderAssistant.setCurrentNamespace(namespace);
    cacheRefElement(context.evalNode("cache-ref"));
    cacheElement(context.evalNode("cache"));
    parameterMapElement(context.evalNodes("/mapper/parameterMap"));
    resultMapElements(context.evalNodes("/mapper/resultMap"));
    sqlElement(context.evalNodes("/mapper/sql"));
    buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
  } catch (Exception e) {
    throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
  }
}

該方法解析一個(gè)xml文件中所有的節(jié)點(diǎn):namespace、cache-ref、cache等,其中解析select|insert|update|delete節(jié)點(diǎn)的方法是buildStatementFromContext

  • XMLMapperBuilder#buildStatementFromContext
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
  for (XNode context : list) {
    final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
    try {
      statementParser.parseStatementNode();
    } catch (IncompleteElementException e) {
      configuration.addIncompleteStatement(statementParser);
    }
  }
}

該方法會(huì)遍歷xml中的所有select|insert|update|delete節(jié)點(diǎn)并解析。其中l(wèi)ist標(biāo)識(shí)所有select|insert|update|delete節(jié)點(diǎn)的結(jié)合。接下來(lái)來(lái)看parseStatementNode這個(gè)方法,它用來(lái)解析單個(gè)select|insert|update|delete節(jié)點(diǎn)

  • XMLStatementBuilder#parseStatementNode
public void parseStatementNode() {
  // 省略解析 id flushCache useCache SelectKey resultType等屬性的過(guò)程
  // 創(chuàng)建SqlSource對(duì)象,也就是解析xml的crud標(biāo)簽,封裝成SqlSource對(duì)象,然后再把SqlSource對(duì)象存入MS對(duì)象中
  SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
  builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
      fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
      resultSetTypeEnum, flushCache, useCache, resultOrdered,
      keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets, dirtySelect);
}

可以看到SqlSource在此被創(chuàng)建了,并且最后作為MappedStatement的屬性存儲(chǔ)在MappedStatement對(duì)象中。這里我們著重關(guān)心SqlSource的創(chuàng)建過(guò)程,它是在createSqlSource方法完成的

  • XMLLanguageDriver#createSqlSource
public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
  XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
  return builder.parseScriptNode();
}

XMLLanguageDriver又委托XMLScriptBuilder解析,接下來(lái)我們看XMLScriptBuilder#parseScriptNode方法

  • XMLScriptBuilder#parseScriptNode
public SqlSource parseScriptNode() {
  MixedSqlNode rootSqlNode = parseDynamicTags(context);
  SqlSource sqlSource;
  if (isDynamic) {
    sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
  } else {
    sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
  }
  return sqlSource;
}

mybatis就是在這個(gè)方法中創(chuàng)建SqlSource對(duì)象,他首先會(huì)調(diào)用parseDynamicTags方法來(lái)解析下節(jié)點(diǎn)是否是動(dòng)態(tài)節(jié)點(diǎn),它的解析過(guò)程就是看節(jié)點(diǎn)是否包含動(dòng)態(tài)標(biāo)簽或包含$占位符,如果滿足任意一個(gè)條件它就會(huì)被解析為動(dòng)態(tài)標(biāo)簽,并創(chuàng)建DynamicSqlSource對(duì)象,否則創(chuàng)建RawSqlSource對(duì)象

SqlSource調(diào)用時(shí)機(jī)

mybatis需要的是可以執(zhí)行的SQL,而通過(guò)SqlSource我們可以獲取BoundSql進(jìn)而獲取BoundSql中的sql字段(該字段就是可執(zhí)行語(yǔ)句)。所以其調(diào)用時(shí)機(jī)是在mybatis進(jìn)行查詢數(shù)據(jù)庫(kù)的時(shí)候——調(diào)用SqlSource#getBoundSql

具體代碼處是Executor執(zhí)行query方法的時(shí)候調(diào)用,源碼在BaseExecutor中,BaseExecutor#query代碼如下

public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
  BoundSql boundSql = ms.getBoundSql(parameter);
  CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
  return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
public BoundSql getBoundSql(Object parameterObject) {
  BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
  return boundSql;
}

我們前文說(shuō)過(guò),SqlSource生成時(shí)會(huì)被存儲(chǔ)在MappedStatement對(duì)象當(dāng)中,所以這里自然也是通過(guò)MappedStatement對(duì)象來(lái)使用SqlSource獲取BoundSql。這樣在mybatis真正調(diào)用JDBC查詢數(shù)據(jù)庫(kù)的時(shí)候就可以通過(guò)BoundSql拿到可執(zhí)行語(yǔ)句啦

總結(jié)

  • SqlSource封裝了XML中的select|insert|update|delete節(jié)點(diǎn),每個(gè)節(jié)點(diǎn)都會(huì)被解析為MixedSqlNode,可以看作是一棵樹,其中包含許多子節(jié)點(diǎn)嵌套
  • 只包含#的sql不算動(dòng)態(tài)節(jié)點(diǎn),只有包含動(dòng)態(tài)標(biāo)簽或者$占位符才算是動(dòng)態(tài)節(jié)點(diǎn)
  • BoundSql中包含了可執(zhí)行sql

本文只是粗略的介紹了SqlSource,只能帶你粗略的了解下mybatis的組件結(jié)構(gòu)。其中SqlSource如何獲取BoundSql對(duì)象,以及節(jié)點(diǎn)到底是如何被解析的,比如if標(biāo)簽是如何進(jìn)行判斷的 等。讀者在理解了這些概念后再閱讀源碼會(huì)容易很多。

以上就是MyBatis SqlSource源碼示例解析的詳細(xì)內(nèi)容,更多關(guān)于MyBatis SqlSource源碼解析的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • springmvc集成shiro登錄失敗處理操作

    springmvc集成shiro登錄失敗處理操作

    這篇文章主要介紹了springmvc集成shiro登錄失敗處理操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2020-09-09
  • Spring JPA聯(lián)表查詢之OneToMany源碼解析

    Spring JPA聯(lián)表查詢之OneToMany源碼解析

    這篇文章主要為大家介紹了Spring JPA聯(lián)表查詢之OneToMany源碼解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-04-04
  • SpringBoot中配置文件及切換方式

    SpringBoot中配置文件及切換方式

    這篇文章主要介紹了SpringBoot中配置文件及切換方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-05-05
  • 解讀maven項(xiàng)目的打包方式

    解讀maven項(xiàng)目的打包方式

    這篇文章主要介紹了關(guān)于maven項(xiàng)目的打包方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-08-08
  • Java線程狀態(tài)變換過(guò)程代碼解析

    Java線程狀態(tài)變換過(guò)程代碼解析

    這篇文章主要介紹了Java線程狀態(tài)變換過(guò)程代碼解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-06-06
  • spring @schedule注解如何動(dòng)態(tài)配置時(shí)間間隔

    spring @schedule注解如何動(dòng)態(tài)配置時(shí)間間隔

    這篇文章主要介紹了spring @schedule注解如何動(dòng)態(tài)配置時(shí)間間隔,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-11-11
  • Maven 多模塊父子工程的實(shí)現(xiàn)(含Spring Boot示例)

    Maven 多模塊父子工程的實(shí)現(xiàn)(含Spring Boot示例)

    這篇文章主要介紹了Maven 多模塊父子工程的實(shí)現(xiàn)(含Spring Boot示例),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2021-04-04
  • SpringBoot 配置文件加密的步驟

    SpringBoot 配置文件加密的步驟

    這篇文章主要介紹了SpringBoot 配置文件加密的步驟,幫助大家更好的理解和學(xué)習(xí)使用springboot框架,感興趣的朋友可以了解下
    2021-03-03
  • Java設(shè)計(jì)模塊系列之書店管理系統(tǒng)單機(jī)版(二)

    Java設(shè)計(jì)模塊系列之書店管理系統(tǒng)單機(jī)版(二)

    這篇文章主要為大家詳細(xì)介紹了Java單機(jī)版的書店管理系統(tǒng)設(shè)計(jì)模塊和思想第二章,感興趣的小伙伴們可以參考一下
    2016-08-08
  • Springboot整合Mybatispuls的實(shí)例詳解

    Springboot整合Mybatispuls的實(shí)例詳解

    這篇文章主要介紹了Springboot整合Mybatispuls的相關(guān)資料,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-11-11

最新評(píng)論

六盘水市| 广昌县| 乃东县| 聊城市| 米脂县| 台北市| 罗江县| 繁昌县| 梁山县| 兴安县| 罗城| 双牌县| 云和县| 九台市| 四平市| 北川| 乌拉特中旗| 白城市| 蓬溪县| 称多县| 湟中县| 孝义市| 航空| 台江县| 阳朔县| 杭锦旗| 仪陇县| 汪清县| 古蔺县| 沙河市| 星座| 益阳市| 兴仁县| 金阳县| 乌恰县| 太仓市| 苍南县| 信宜市| 团风县| 长子县| 阳江市|