欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

MyBatis?SqlSource源碼示例解析

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

正文

MyBatis版本:3.5.12。

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

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

簡單的示意圖就是

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

SqlNode

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

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

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

接下來我們來用一個案例分析mybatis是如何把一個<select>節(jié)點(diǎn)解析為一個SqlNode對象的(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>

它會被解析成如下這樣一顆SqlNode樹

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

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

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

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

SqlNode接口定義

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

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

mybatis會在解析過程中把select標(biāo)簽解析為如上分析的一棵樹MixedSqlNode然后就會遞歸遍歷這些SqlNode并調(diào)用他們的apply方法,調(diào)用apply方法實(shí)際上就是把標(biāo)簽解析后的sql片段拼接到了context中的sql字段。最后只需要調(diào)用context.getSql方法就可以獲得可執(zhí)行SQL了。而一切都從根節(jié)點(diǎn)的apply方法說起,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中有一個List字段,該字段存儲的是樹的葉子節(jié)點(diǎn),在這個示例中,List字段中應(yīng)該由兩個SqlNode

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

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

context.appendSql(text);

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

BoundSql

知道了什么是SqlNode之后,我們再來看BoundSql,BoundSql內(nèi)部封裝了可執(zhí)行SQL,先來看下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é)說到的SqlNode調(diào)用完apply方法后存儲在DynamicContext中的sql就會被賦值給該字段。sql字段其實(shí)就是類似于select * from user where id = ? 這樣的字符串,
  • parameterObject:用戶傳入的屬性,用于給sql字段的?賦值
  • additionalParameters: bind標(biāo)簽中綁定的值會存儲在此
  • metaParameters:additionalParameters的元類型

還記得開篇我們說的目標(biāo)嗎?我貼過來再看一遍

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

簡單的示意圖就是

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

SqlSource

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

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

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

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

SqlSource解析時機(jī)

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

答案是在mybatis啟動時,會加載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);
  }
}

該方法解析一個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);
    }
  }
}

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

  • XMLStatementBuilder#parseStatementNode
public void parseStatementNode() {
  // 省略解析 id flushCache useCache SelectKey resultType等屬性的過程
  // 創(chuàng)建SqlSource對象,也就是解析xml的crud標(biāo)簽,封裝成SqlSource對象,然后再把SqlSource對象存入MS對象中
  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的屬性存儲在MappedStatement對象中。這里我們著重關(guān)心SqlSource的創(chuàng)建過程,它是在createSqlSource方法完成的

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

XMLLanguageDriver又委托XMLScriptBuilder解析,接下來我們看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就是在這個方法中創(chuàng)建SqlSource對象,他首先會調(diào)用parseDynamicTags方法來解析下節(jié)點(diǎn)是否是動態(tài)節(jié)點(diǎn),它的解析過程就是看節(jié)點(diǎn)是否包含動態(tài)標(biāo)簽或包含$占位符,如果滿足任意一個條件它就會被解析為動態(tài)標(biāo)簽,并創(chuàng)建DynamicSqlSource對象,否則創(chuàng)建RawSqlSource對象

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

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

具體代碼處是Executor執(zhí)行query方法的時候調(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;
}

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

總結(jié)

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

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

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

相關(guān)文章

  • Java List集合返回值去掉中括號(''[ ]'')的操作

    Java List集合返回值去掉中括號(''[ ]'')的操作

    這篇文章主要介紹了Java List集合返回值去掉中括號('[ ]')的操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-08-08
  • 分布式系統(tǒng)中的降級熔斷設(shè)計問題面試

    分布式系統(tǒng)中的降級熔斷設(shè)計問題面試

    這篇文章主要為大家介紹了分布式系統(tǒng)中的降級熔斷設(shè)計問題面試解答,有需要的朋友可以借鑒參考下,希望能有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-03-03
  • 解決dubbo啟動報服務(wù)注冊失敗Failed?to?register?dubbo

    解決dubbo啟動報服務(wù)注冊失敗Failed?to?register?dubbo

    這篇文章主要介紹了解決dubbo啟動報服務(wù)注冊失敗Failed?to?register?dubbo問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2023-12-12
  • Java中Array、List、ArrayList的區(qū)別及說明

    Java中Array、List、ArrayList的區(qū)別及說明

    這篇文章主要介紹了Java中Array、List、ArrayList的區(qū)別及說明,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-07-07
  • 如何通過Java實(shí)現(xiàn)修改視頻分辨率

    如何通過Java實(shí)現(xiàn)修改視頻分辨率

    Java除了可以修改圖片的分辨率,還可以實(shí)現(xiàn)修改視頻的分辨率,這篇文章就將帶大家學(xué)習(xí)如果編寫這一工具類,感興趣的同學(xué)可以了解一下
    2021-12-12
  • java  基礎(chǔ)知識之IO總結(jié)

    java 基礎(chǔ)知識之IO總結(jié)

    這篇文章主要介紹了java 基礎(chǔ)知識之IO總結(jié)的相關(guān)資料,Java中的I/O分為兩種類型,一種是順序讀取,一種是隨機(jī)讀取,需要的朋友可以參考下
    2017-03-03
  • JDBC的擴(kuò)展知識點(diǎn)總結(jié)

    JDBC的擴(kuò)展知識點(diǎn)總結(jié)

    這篇文章主要介紹了JDBC的擴(kuò)展知識點(diǎn)總結(jié),文中有非常詳細(xì)的代碼示例,對正在學(xué)習(xí)JDBC的小伙伴們有很好地幫助,需要的朋友可以參考下
    2021-05-05
  • java實(shí)現(xiàn)ArrayList根據(jù)存儲對象排序功能示例

    java實(shí)現(xiàn)ArrayList根據(jù)存儲對象排序功能示例

    這篇文章主要介紹了java實(shí)現(xiàn)ArrayList根據(jù)存儲對象排序功能,結(jié)合實(shí)例形式分析了java針對ArrayList的相關(guān)運(yùn)算、排序操作技巧,需要的朋友可以參考下
    2018-01-01
  • 一篇文章帶你了解java接口與繼承

    一篇文章帶你了解java接口與繼承

    這篇文章主要介紹了Java接口和繼承操作,結(jié)合具體實(shí)例形式分析了Java接口和繼承與使用的相關(guān)原理、操作技巧與注意事項(xiàng),需要的朋友可以參考下
    2021-08-08
  • binarySearch在java的查找實(shí)例用法

    binarySearch在java的查找實(shí)例用法

    在本篇文章里小編給大家整理的是一篇關(guān)于binarySearch在java的查找實(shí)例用法,對此有興趣的朋友們可以學(xué)習(xí)參考下。
    2021-02-02

最新評論