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

MyBatis?SqlSource源碼示例解析

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

正文

MyBatis版本:3.5.12。

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

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

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

接下來我們先來介紹幾個(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)。

接下來我們來用一個(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

  • 然后與接下來是一個(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(沒錯(cuò),即使它有#符,它不屬于靜態(tài)文本哦。只有包含$才算動(dòng)態(tài)節(jié)點(diǎn))
  • 而第二個(gè)if標(biāo)簽中的文本包含$會(huì)被解析成TextSqlNode

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

SqlNode接口定義

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

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

mybatis會(huì)在解析過程中把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方法說起,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),這里就不展開說了,我們只需要知道,所有的SqlNode都會(huì)遞歸執(zhí)行apply方法,而apply方法只做了一件事——那就是把SqlNode節(jié)點(diǎn)中的文本經(jīng)過一系列規(guī)則解析過后(通常就是刪除標(biāo)簽,刪除無用的and|or,刪除無用的,等),返回可執(zhí)行SQL的片段,這些SQL片段最終都會(huì)以如下方法把sql片段拼接,

context.appendSql(text);

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

BoundSql

知道了什么是SqlNode之后,我們?cè)賮砜?code>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方法后存儲(chǔ)在DynamicContext中的sql就會(huì)被賦值給該字段。sql字段其實(shí)就是類似于select * from user where id = ? 這樣的字符串,
  • parameterObject:用戶傳入的屬性,用于給sql字段的?賦值
  • additionalParameters: bind標(biāo)簽中綁定的值會(huì)存儲(chǔ)在此
  • metaParameters:additionalParameters的元類型

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

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

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

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

SqlSource

為了完成開篇說的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è)。前文有說)。注意只包含#占位符表達(dá)式的語句不會(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é)合。接下來來看parseStatementNode這個(gè)方法,它用來解析單個(gè)select|insert|update|delete節(jié)點(diǎn)

  • XMLStatementBuilder#parseStatementNode
public void parseStatementNode() {
  // 省略解析 id flushCache useCache SelectKey resultType等屬性的過程
  // 創(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)建過程,它是在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就是在這個(gè)方法中創(chuàng)建SqlSource對(duì)象,他首先會(huì)調(diào)用parseDynamicTags方法來解析下節(jié)點(diǎn)是否是動(dòng)態(tài)節(jié)點(diǎn),它的解析過程就是看節(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,而通過SqlSource我們可以獲取BoundSql進(jìn)而獲取BoundSql中的sql字段(該字段就是可執(zhí)行語句)。所以其調(diào)用時(shí)機(jī)是在mybatis進(jìn)行查詢數(shù)據(jù)庫的時(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;
}

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

總結(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)文章

最新評(píng)論