強(qiáng)大的動(dòng)態(tài)SQL

1 動(dòng)態(tài)SQL#

那么瓦呼,問(wèn)題來(lái)了: 什么是動(dòng)態(tài)SQL? 動(dòng)態(tài)SQL有什么作用涨共?

傳統(tǒng)的使用JDBC的方法岖寞,相信大家在組合復(fù)雜的的SQL語(yǔ)句的時(shí)候抡四,需要去拼接,稍不注意哪怕少了個(gè)空格仗谆,都會(huì)導(dǎo)致錯(cuò)誤指巡。Mybatis的動(dòng)態(tài)SQL功能正是為了解決這種問(wèn)題, 其通過(guò) if, choose, when, otherwise, trim, where, set, foreach標(biāo)簽隶垮,可組合成非常靈活的SQL語(yǔ)句藻雪,從而提高開(kāi)發(fā)人員的效率。下面就去感受Mybatis動(dòng)態(tài)SQL的魅力吧狸吞。

2 if: 你們能判斷勉耀,我也能判斷指煎!#

作為程序猿,誰(shuí)不懂 if ! 在mybatis中也能用 if 啦:

<select id="findUserById" resultType="user">
    select * from user where 
        <if test="id != null">
               id=#{id}
        </if>
    and deleteFlag=0;
</select>

上面例子: 如果傳入的id 不為空便斥, 那么才會(huì)SQL才拼接id = #{id}至壤。 這個(gè)相信大家看一樣就能明白,不多說(shuō)枢纠。細(xì)心的人會(huì)發(fā)現(xiàn)一個(gè)問(wèn)題:“你這不對(duì)俺缟! 要是你傳入的id為null, 那么你這最終的SQL語(yǔ)句不就成了 select * from user where and deleteFlag=0, 這語(yǔ)句有問(wèn)題京郑!”

是啊,這時(shí)候葫掉,mybatis的 where 標(biāo)簽就該隆重登場(chǎng)啦些举。

3 where, 有了我,SQL語(yǔ)句拼接條件神馬的都是浮云俭厚!#

咱們通過(guò)where改造一下上面的例子:

<select id="findUserById" resultType="user">
    select * from user 
        <where>
            <if test="id != null">
                id=#{id}
            </if>
            and deleteFlag=0;
        </where>
</select>

有些人就要問(wèn)了: “你這都是些什么玩意兒户魏! 跟上面的相比, 不就是多了個(gè)where標(biāo)簽嘛挪挤! 那這個(gè)還會(huì)不會(huì)出現(xiàn) select * from user where and deleteFlag=0 叼丑?”

的確,從表面上來(lái)看扛门,就是多了個(gè)where標(biāo)簽而已鸠信, 不過(guò)實(shí)質(zhì)上, mybatis是對(duì)它做了處理论寨,當(dāng)它遇到AND或者OR這些星立,它知道怎么處理。其實(shí)我們可以通過(guò) trim 標(biāo)簽去自定義這種處理規(guī)則葬凳。

4 trim : 我的地盤(pán)绰垂,我做主!#

上面的where標(biāo)簽火焰,其實(shí)用trim 可以表示如下:

<trim prefix="WHERE" prefixOverrides="AND |OR ">
    ... 
</trim>

它的意思就是:當(dāng)WHERE后緊隨AND或則OR的時(shí)候劲装,就去除AND或者OR。 除了WHERE以外昌简,其實(shí)還有一個(gè)比較經(jīng)典的實(shí)現(xiàn)占业,那就是SET。

5 set: 信我纯赎,不出錯(cuò)纺酸!#

<update id="updateUser" parameterType="com.dy.entity.User">
    update user set 
        <if test="name != null">
            name = #{name},
        </if> 
        <if test="password != null">
            password = #{password},
        </if> 
        <if test="age != null">
            age = #{age}
        </if> 
        <where>
            <if test="id != null">
                id = #{id}
            </if>
            and deleteFlag = 0;
        </where>
</update>

問(wèn)題又來(lái)了: “如果我只有name不為null, 那么這SQL不就成了 update set name = #{name}, where ........ ? 你那name后面那逗號(hào)會(huì)導(dǎo)致出錯(cuò)啊址否!”

是的餐蔬,這時(shí)候碎紊,就可以用mybatis為我們提供的set 標(biāo)簽了。下面是通過(guò)set標(biāo)簽改造后:

<update id="updateUser" parameterType="com.dy.entity.User">
    update user
        <set>
            <if test="name != null">name = #{name},</if> 
            <if test="password != null">password = #{password},</if> 
            <if test="age != null">age = #{age},</if> 
        </set>
        <where>
            <if test="id != null">
                id = #{id}
            </if>
            and deleteFlag = 0;
        </where>
</update>

這個(gè)用trim 可表示為:

<trim prefix="SET" suffixOverrides=",">
  ...
</trim>

WHERE是使用的 prefixOverrides(前綴)樊诺, SET是使用的 suffixOverrides (后綴)仗考, 看明白了吧!

6 foreach: 你有for, 我有foreach, 不要以為就你才屌词爬!#

java中有for, 可通過(guò)for循環(huán)秃嗜, 同樣, mybatis中有foreach, 可通過(guò)它實(shí)現(xiàn)循環(huán)顿膨,循環(huán)的對(duì)象當(dāng)然主要是java容器和數(shù)組锅锨。

<select id="selectPostIn" resultType="domain.blog.Post">
    SELECT *
    FROM POST P
    WHERE ID in
    <foreach item="item" index="index" collection="list"
        open="(" separator="," close=")">
        #{item}
    </foreach>
</select>

將一個(gè) List 實(shí)例或者數(shù)組作為參數(shù)對(duì)象傳給 MyBatis:當(dāng)這么做的時(shí)候,MyBatis 會(huì)自動(dòng)將它包裝在一個(gè) Map 中并以名稱(chēng)為鍵恋沃。List 實(shí)例將會(huì)以“l(fā)ist”作為鍵必搞,而數(shù)組實(shí)例的鍵將是“array”。

同樣囊咏,當(dāng)循環(huán)的對(duì)象為map的時(shí)候恕洲,index其實(shí)就是map的key。

7 choose: 我選擇了你梅割,你選擇了我霜第!#

Java中有switch, mybatis有choose。

<select id="findActiveBlogLike"
     resultType="Blog">
    SELECT * FROM BLOG WHERE state = ‘ACTIVE’
    <choose>
        <when test="title != null">
            AND title like #{title}
        </when>
        <when test="author != null and author.name != null">
            AND author_name like #{author.name}
        </when>
        <otherwise>
            AND featured = 1
        </otherwise>
    </choose>
</select>

以上例子中:當(dāng)title和author都不為null的時(shí)候户辞, 那么選擇二選一(前者優(yōu)先)泌类, 如果都為null, 那么就選擇 otherwise中的, 如果tilte和author只有一個(gè)不為null, 那么就選擇不為null的那個(gè)底燎。

8 動(dòng)態(tài)SQL解析原理#

我們?cè)谑褂胢ybatis的時(shí)候末誓,會(huì)在xml中編寫(xiě)sql語(yǔ)句。比如這段動(dòng)態(tài)sql代碼:

<update id="update" parameterType="org.format.dynamicproxy.mybatis.bean.User">
    UPDATE users
    <trim prefix="SET" prefixOverrides=",">
        <if test="name != null and name != ''">
            name = #{name}
        </if>
        <if test="age != null and age != ''">
            , age = #{age}
        </if>
        <if test="birthday != null and birthday != ''">
            , birthday = #{birthday}
        </if>
    </trim>
    where id = ${id}
</update>

mybatis底層是如何構(gòu)造這段sql的书蚪?下面帶著這個(gè)疑問(wèn)喇澡,我們一步一步分析。

8.1 關(guān)于動(dòng)態(tài)SQL的接口和類(lèi)##

  1. SqlNode接口殊校,簡(jiǎn)單理解就是xml中的每個(gè)標(biāo)簽晴玖,比如上述sql的update,trim,if標(biāo)簽:
public interface SqlNode {
      boolean apply(DynamicContext context);
}
SqlNode相關(guān)類(lèi)圖
  1. SqlSource Sql源接口,代表從xml文件或注解映射的sql內(nèi)容为流,主要就是用于創(chuàng)建BoundSql呕屎,有實(shí)現(xiàn)類(lèi)DynamicSqlSource(動(dòng)態(tài)Sql源),StaticSqlSource(靜態(tài)Sql源)等:
public interface SqlSource {
      BoundSql getBoundSql(Object parameterObject);
}
SqlSource相關(guān)類(lèi)圖
  1. BoundSql類(lèi)敬察,封裝mybatis最終產(chǎn)生sql的類(lèi)秀睛,包括sql語(yǔ)句,參數(shù)莲祸,參數(shù)源數(shù)據(jù)等參數(shù):
BoundSql類(lèi)
  1. XNode蹂安,一個(gè)Dom API中的Node接口的擴(kuò)展類(lèi):
XNode類(lèi)
  1. BaseBuilder接口及其實(shí)現(xiàn)類(lèi)(屬性椭迎,方法省略了,大家有興趣的自己看),這些Builder的作用就是用于構(gòu)造sql:
BaseBuilder相關(guān)類(lèi)圖

下面我們簡(jiǎn)單分析下其中4個(gè)Builder:

XMLConfigBuilder:解析mybatis中configLocation屬性中的全局xml文件田盈,內(nèi)部會(huì)使用XMLMapperBuilder解析各個(gè)xml文件畜号。

XMLMapperBuilder:遍歷mybatis中mapperLocations屬性中的xml文件中每個(gè)節(jié)點(diǎn)的Builder,比如user.xml允瞧,內(nèi)部會(huì)使用XMLStatementBuilder處理xml中的每個(gè)節(jié)點(diǎn)简软。

XMLStatementBuilder:解析xml文件中各個(gè)節(jié)點(diǎn),比如select,insert,update,delete節(jié)點(diǎn)述暂,內(nèi)部會(huì)使用XMLScriptBuilder處理節(jié)點(diǎn)的sql部分痹升,遍歷產(chǎn)生的數(shù)據(jù)會(huì)丟到Configuration的mappedStatements中。

XMLScriptBuilder:解析xml中各個(gè)節(jié)點(diǎn)sql部分的Builder畦韭。

  1. LanguageDriver接口及其實(shí)現(xiàn)類(lèi)(屬性疼蛾,方法省略了,大家有興趣的自己看)廊驼,該接口主要的作用就是構(gòu)造sql:
LanguageDriver相關(guān)類(lèi)圖

簡(jiǎn)單分析下XMLLanguageDriver(處理xml中的sql,RawLanguageDriver處理靜態(tài)sql):XMLLanguageDriver內(nèi)部會(huì)使用XMLScriptBuilder解析xml中的sql部分惋砂。

8.2 源碼分析走起##

Spring與Mybatis整合的時(shí)候需要配置SqlSessionFactoryBean妒挎,該配置會(huì)加入數(shù)據(jù)源和mybatis xml配置文件路徑等信息:

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <property name="configLocation" value="classpath:mybatisConfig.xml"/>
    <property name="mapperLocations" value="classpath*:org/format/dao/*.xml"/>
</bean>

我們就分析這一段配置背后的細(xì)節(jié):

SqlSessionFactoryBean實(shí)現(xiàn)了Spring的InitializingBean接口,InitializingBean接口的afterPropertiesSet方法中會(huì)調(diào)用buildSqlSessionFactory方法西饵,該方法內(nèi)部會(huì)使用XMLConfigBuilder解析屬性configLocation中配置的路徑酝掩,還會(huì)使用XMLMapperBuilder屬性解析mapperLocations屬性中的各個(gè)xml文件。部分源碼如下:

  protected SqlSessionFactory buildSqlSessionFactory() throws IOException {

      Configuration configuration;

      XMLConfigBuilder xmlConfigBuilder = null;
      if (this.configLocation != null) {
          // 1. 構(gòu)建XMLConfigBuilder
          xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
          configuration = xmlConfigBuilder.getConfiguration();
      } else {
          if (logger.isDebugEnabled()) {
              logger.debug("Property 'configLocation' not specified, using default MyBatis Configuration");
          }
          configuration = new Configuration();
          configuration.setVariables(this.configurationProperties);
      }

      if (this.objectFactory != null) {
          configuration.setObjectFactory(this.objectFactory);
      }

      if (this.objectWrapperFactory != null) {
          configuration.setObjectWrapperFactory(this.objectWrapperFactory);
      }

      if (hasLength(this.typeAliasesPackage)) {
          String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage,
          ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
          for (String packageToScan : typeAliasPackageArray) {
              configuration.getTypeAliasRegistry().registerAliases(packageToScan,
                typeAliasesSuperType == null ? Object.class : typeAliasesSuperType);
              if (logger.isDebugEnabled()) {
                  logger.debug("Scanned package: '" + packageToScan + "' for aliases");
              }
          }
      }

      if (!isEmpty(this.typeAliases)) {
          for (Class<?> typeAlias : this.typeAliases) {
              configuration.getTypeAliasRegistry().registerAlias(typeAlias);
              if (logger.isDebugEnabled()) {
                  logger.debug("Registered type alias: '" + typeAlias + "'");
              }
          }
      }

      if (!isEmpty(this.plugins)) {
          for (Interceptor plugin : this.plugins) {
              configuration.addInterceptor(plugin);
              if (logger.isDebugEnabled()) {
                  logger.debug("Registered plugin: '" + plugin + "'");
              }
          }
      }

      if (hasLength(this.typeHandlersPackage)) {
          String[] typeHandlersPackageArray = tokenizeToStringArray(this.typeHandlersPackage,
          ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
          for (String packageToScan : typeHandlersPackageArray) {
              configuration.getTypeHandlerRegistry().register(packageToScan);
              if (logger.isDebugEnabled()) {
                  logger.debug("Scanned package: '" + packageToScan + "' for type handlers");
              }
          }
      }

      if (!isEmpty(this.typeHandlers)) {
          for (TypeHandler<?> typeHandler : this.typeHandlers) {
              configuration.getTypeHandlerRegistry().register(typeHandler);
              if (logger.isDebugEnabled()) {
                  logger.debug("Registered type handler: '" + typeHandler + "'");
              }
          }
      }

      if (xmlConfigBuilder != null) {
          try {
              // 2. 解析xmlConfigBuilder
              xmlConfigBuilder.parse();

              if (logger.isDebugEnabled()) {
                  logger.debug("Parsed configuration file: '" + this.configLocation + "'");
              }
          } catch (Exception ex) {
              throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
          } finally {
              ErrorContext.instance().reset();
          }
      }

      if (this.transactionFactory == null) {
          this.transactionFactory = new SpringManagedTransactionFactory();
      }

      Environment environment = new Environment(this.environment, this.transactionFactory, this.dataSource);
      configuration.setEnvironment(environment);

      if (this.databaseIdProvider != null) {
          try {
              configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
          } catch (SQLException e) {
              throw new NestedIOException("Failed getting a databaseId", e);
          }
      }

      if (!isEmpty(this.mapperLocations)) {
          for (Resource mapperLocation : this.mapperLocations) {
              if (mapperLocation == null) {
                  continue;
              }

              try {
                  // 3. 構(gòu)建XMLMapperBuilder眷柔,并解析Mapper文件
                  XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
              configuration, mapperLocation.toString(), configuration.getSqlFragments());
                  xmlMapperBuilder.parse();
              } catch (Exception e) {
                  throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
              } finally {
                  ErrorContext.instance().reset();
              }

              if (logger.isDebugEnabled()) {
                  logger.debug("Parsed mapper file: '" + mapperLocation + "'");
              }
          }
      } else {
          if (logger.isDebugEnabled()) {
              logger.debug("Property 'mapperLocations' was not specified or no matching resources found");
          }
      }

      return this.sqlSessionFactoryBuilder.build(configuration);
  }

再來(lái)看下期虾,XMLConfigBudiler.parse()方法源碼細(xì)節(jié):

  public Configuration parse() {
      if (parsed) {
          throw new BuilderException("Each XMLConfigBuilder can only be used once.");
      }
      parsed = true;
      parseConfiguration(parser.evalNode("/configuration"));
      return configuration;
  }

  private void parseConfiguration(XNode root) {
      try {
          propertiesElement(root.evalNode("properties")); //issue #117 read properties first
          typeAliasesElement(root.evalNode("typeAliases"));
          pluginElement(root.evalNode("plugins"));
          objectFactoryElement(root.evalNode("objectFactory"));
          objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
          settingsElement(root.evalNode("settings"));
          environmentsElement(root.evalNode("environments")); // read it after objectFactory and objectWrapperFactory issue #631
          databaseIdProviderElement(root.evalNode("databaseIdProvider"));
          typeHandlerElement(root.evalNode("typeHandlers"));
          // 解析Mapper映射文件
          mapperElement(root.evalNode("mappers"));
      } catch (Exception e) {
          throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
      }
  }

  private void mapperElement(XNode parent) throws Exception {
      if (parent != null) {
          for (XNode child : parent.getChildren()) {
              if ("package".equals(child.getName())) {
                  String mapperPackage = child.getStringAttribute("name");
                  configuration.addMappers(mapperPackage);
              } else {
                  String resource = child.getStringAttribute("resource");
                  String url = child.getStringAttribute("url");
                  String mapperClass = child.getStringAttribute("class");
                  if (resource != null && url == null && mapperClass == null) {
                      ErrorContext.instance().resource(resource);
                      InputStream inputStream = Resources.getResourceAsStream(resource);
                      // 構(gòu)建XMLMapperBuilder對(duì)象
                      XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
                      mapperParser.parse();
                  } else if (resource == null && url != null && mapperClass == null) {
                      ErrorContext.instance().resource(url);
                      InputStream inputStream = Resources.getUrlAsStream(url);
                      // 構(gòu)建XMLMapperBuilder對(duì)象
                      XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
                      mapperParser.parse();
                  } else if (resource == null && url == null && mapperClass != null) {
                      Class<?> mapperInterface = Resources.classForName(mapperClass);
                      configuration.addMapper(mapperInterface);
                  } else {
                      throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
                  }
              }
          }
      }
  }

由于XMLConfigBuilder內(nèi)部也是使用XMLMapperBuilder,我們就看看XMLMapperBuilder的解析細(xì)節(jié):

XMLMapperBuilder.parse()源碼
XMLMapperBuilder.configurationElement()源碼

我們關(guān)注一下驯嘱,增刪改查節(jié)點(diǎn)的解析:

增刪改查節(jié)點(diǎn)的解析

XMLStatementBuilder的解析:

XMLStatementBuilder的解析

默認(rèn)會(huì)使用XMLLanguageDriver創(chuàng)建SqlSource(Configuration構(gòu)造函數(shù)中設(shè)置)镶苞。

XMLLanguageDriver創(chuàng)建SqlSource:

XMLLanguageDriver創(chuàng)建SqlSource

XMLScriptBuilder解析sql:

XMLScriptBuilder解析sql

得到SqlSource之后,會(huì)放到Configuration中鞠评,有了SqlSource茂蚓,就能拿BoundSql了,BoundSql可以得到最終的sql剃幌。

8.3 實(shí)例分析##

以下面的xml解析大概說(shuō)下parseDynamicTags的解析過(guò)程:

<update id="update" parameterType="org.format.dynamicproxy.mybatis.bean.User">
    UPDATE users
    <trim prefix="SET" prefixOverrides=",">
        <if test="name != null and name != ''">
            name = #{name}
        </if>
        <if test="age != null and age != ''">
            , age = #{age}
        </if>
        <if test="birthday != null and birthday != ''">
            , birthday = #{birthday}
        </if>
    </trim>
    where id = ${id}
</update>

parseDynamicTags方法的返回值是一個(gè)List聋涨,也就是一個(gè)Sql節(jié)點(diǎn)集合。SqlNode本文一開(kāi)始已經(jīng)介紹负乡,分析完解析過(guò)程之后會(huì)說(shuō)一下各個(gè)SqlNode類(lèi)型的作用牍白。

  1. 首先根據(jù)update節(jié)點(diǎn)(Node)得到所有的子節(jié)點(diǎn),分別是3個(gè)子節(jié)點(diǎn):

(1) 文本節(jié)點(diǎn) \n UPDATE users抖棘;

(2) trim子節(jié)點(diǎn) ...茂腥;

(3) 文本節(jié)點(diǎn) \n where id = #{id}狸涌;

  1. 遍歷各個(gè)子節(jié)點(diǎn):

(1) 如果節(jié)點(diǎn)類(lèi)型是文本或者CDATA,構(gòu)造一個(gè)TextSqlNode或StaticTextSqlNode础芍;

(2) 如果節(jié)點(diǎn)類(lèi)型是元素杈抢,說(shuō)明該update節(jié)點(diǎn)是個(gè)動(dòng)態(tài)sql,然后會(huì)使用NodeHandler處理各個(gè)類(lèi)型的子節(jié)點(diǎn)仑性。這里的NodeHandler是XMLScriptBuilder的一個(gè)內(nèi)部接口惶楼,其實(shí)現(xiàn)類(lèi)包括TrimHandler、WhereHandler诊杆、SetHandler歼捐、IfHandler、ChooseHandler等晨汹”ⅲ看類(lèi)名也就明白了這個(gè)Handler的作用,比如我們分析的trim節(jié)點(diǎn)淘这,對(duì)應(yīng)的是TrimHandler剥扣;if節(jié)點(diǎn),對(duì)應(yīng)的是IfHandler...這里子節(jié)點(diǎn)trim被TrimHandler處理铝穷,TrimHandler內(nèi)部也使用parseDynamicTags方法解析節(jié)點(diǎn)钠怯。

  1. 遇到子節(jié)點(diǎn)是元素的話(huà),重復(fù)以上步驟:

trim子節(jié)點(diǎn)內(nèi)部有7個(gè)子節(jié)點(diǎn)曙聂,分別是文本節(jié)點(diǎn)晦炊、if節(jié)點(diǎn)、是文本節(jié)點(diǎn)宁脊、if節(jié)點(diǎn)断国、是文本節(jié)點(diǎn)、if節(jié)點(diǎn)榆苞、文本節(jié)點(diǎn)稳衬。文本節(jié)點(diǎn)跟之前一樣處理,if節(jié)點(diǎn)使用IfHandler處理坐漏。遍歷步驟如上所示宋彼,下面我們看下幾個(gè)Handler的實(shí)現(xiàn)細(xì)節(jié)。

IfHandler處理方法也是使用parseDynamicTags方法仙畦,然后加上if標(biāo)簽必要的屬性:

private class IfHandler implements NodeHandler {
      public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
          List<SqlNode> contents = parseDynamicTags(nodeToHandle);
          MixedSqlNode mixedSqlNode = new MixedSqlNode(contents);
          String test = nodeToHandle.getStringAttribute("test");
          IfSqlNode ifSqlNode = new IfSqlNode(mixedSqlNode, test);
          targetContents.add(ifSqlNode);
      }
}

TrimHandler處理方法也是使用parseDynamicTags方法输涕,然后加上trim標(biāo)簽必要的屬性:

private class TrimHandler implements NodeHandler {
      public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
          List<SqlNode> contents = parseDynamicTags(nodeToHandle);
          MixedSqlNode mixedSqlNode = new MixedSqlNode(contents);
          String prefix = nodeToHandle.getStringAttribute("prefix");
          String prefixOverrides = nodeToHandle.getStringAttribute("prefixOverrides");
          String suffix = nodeToHandle.getStringAttribute("suffix");
          String suffixOverrides = nodeToHandle.getStringAttribute("suffixOverrides");
          TrimSqlNode trim = new TrimSqlNode(configuration, mixedSqlNode, prefix, prefixOverrides, suffix, suffixOverrides);
          targetContents.add(trim);
      }
}
  1. 以上update方法最終通過(guò)parseDynamicTags方法得到的SqlNode集合如下:
Paste_Image.png

trim節(jié)點(diǎn):

Paste_Image.png

由于這個(gè)update方法是個(gè)動(dòng)態(tài)節(jié)點(diǎn),因此構(gòu)造出了DynamicSqlSource慨畸。DynamicSqlSource內(nèi)部就可以構(gòu)造sql了:

Paste_Image.png

DynamicSqlSource內(nèi)部的SqlNode屬性是一個(gè)MixedSqlNode莱坎。然后我們看看各個(gè)SqlNode實(shí)現(xiàn)類(lèi)的apply方法。下面分析一下各個(gè)SqlNode實(shí)現(xiàn)類(lèi)的apply方法實(shí)現(xiàn):

  1. MixedSqlNode:MixedSqlNode會(huì)遍歷調(diào)用內(nèi)部各個(gè)sqlNode的apply方法寸士。
public boolean apply(DynamicContext context) {
     for (SqlNode sqlNode : contents) {
         sqlNode.apply(context);
     }
     return true;
}
  1. StaticTextSqlNode:直接append sql文本檐什。
public boolean apply(DynamicContext context) {
     context.appendSql(text);
     return true;
}
  1. IfSqlNode:這里的evaluator是一個(gè)ExpressionEvaluator類(lèi)型的實(shí)例碴卧,內(nèi)部使用了OGNL處理表達(dá)式邏輯。
public boolean apply(DynamicContext context) {
     if (evaluator.evaluateBoolean(test, context.getBindings())) {
         contents.apply(context);
         return true;
     }
     return false;
}
  1. TrimSqlNode:
public boolean apply(DynamicContext context) {
      FilteredDynamicContext filteredDynamicContext = new FilteredDynamicContext(context);
      boolean result = contents.apply(filteredDynamicContext);
      filteredDynamicContext.applyAll();
      return result;
}

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

private void applyPrefix(StringBuilder sql, String trimmedUppercaseSql) {
      if (!prefixApplied) {
          prefixApplied = true;
          if (prefixesToOverride != null) {
              for (String toRemove : prefixesToOverride) {
                  if (trimmedUppercaseSql.startsWith(toRemove)) {
                      sql.delete(0, toRemove.trim().length());
                      break;
                  }
              }
          }
          if (prefix != null) {
              sql.insert(0, " ");
              sql.insert(0, prefix);
          }
     }
}

TrimSqlNode的apply方法也是調(diào)用屬性contents(一般都是MixedSqlNode)的apply方法乃正,按照實(shí)例也就是7個(gè)SqlNode住册,都是StaticTextSqlNode和IfSqlNode。 最后會(huì)使用FilteredDynamicContext過(guò)濾掉prefix和suffix瓮具。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末荧飞,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子名党,更是在濱河造成了極大的恐慌叹阔,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,639評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件传睹,死亡現(xiàn)場(chǎng)離奇詭異耳幢,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)欧啤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,277評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén)睛藻,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人邢隧,你說(shuō)我怎么就攤上這事店印。” “怎么了府框?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,221評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵吱窝,是天一觀的道長(zhǎng)讥邻。 經(jīng)常有香客問(wèn)我迫靖,道長(zhǎng),這世上最難降的妖魔是什么兴使? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,474評(píng)論 1 283
  • 正文 為了忘掉前任系宜,我火速辦了婚禮,結(jié)果婚禮上发魄,老公的妹妹穿的比我還像新娘盹牧。我一直安慰自己,他們只是感情好励幼,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,570評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布汰寓。 她就那樣靜靜地躺著,像睡著了一般苹粟。 火紅的嫁衣襯著肌膚如雪有滑。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,816評(píng)論 1 290
  • 那天嵌削,我揣著相機(jī)與錄音毛好,去河邊找鬼望艺。 笑死,一個(gè)胖子當(dāng)著我的面吹牛肌访,可吹牛的內(nèi)容都是我干的找默。 我是一名探鬼主播,決...
    沈念sama閱讀 38,957評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼吼驶,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼惩激!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起旨剥,我...
    開(kāi)封第一講書(shū)人閱讀 37,718評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤咧欣,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后轨帜,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體魄咕,經(jīng)...
    沈念sama閱讀 44,176評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,511評(píng)論 2 327
  • 正文 我和宋清朗相戀三年蚌父,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了哮兰。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,646評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡苟弛,死狀恐怖喝滞,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情膏秫,我是刑警寧澤右遭,帶...
    沈念sama閱讀 34,322評(píng)論 4 330
  • 正文 年R本政府宣布,位于F島的核電站缤削,受9級(jí)特大地震影響窘哈,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜亭敢,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,934評(píng)論 3 313
  • 文/蒙蒙 一滚婉、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧帅刀,春花似錦让腹、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,755評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至锥余,卻和暖如春腹纳,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,987評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工只估, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留志群,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,358評(píng)論 2 360
  • 正文 我出身青樓蛔钙,卻偏偏與公主長(zhǎng)得像锌云,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子吁脱,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,514評(píng)論 2 348

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