zoukankan      html  css  js  c++  java
  • 动态SQL

    1 动态SQL

    那么,问题来了: 什么是动态SQL? 动态SQL有什么作用?

    传统的使用JDBC的方法,相信大家在组合复杂的的SQL语句的时候,需要去拼接,稍不注意哪怕少了个空格,都会导致错误。Mybatis的动态SQL功能正是为了解决这种问题, 其通过 if, choose, when, otherwise, trim, where, set, foreach标签,可组合成非常灵活的SQL语句,从而提高开发人员的效率。下面就去感受Mybatis动态SQL的魅力吧。

    2 if: 你们能判断,我也能判断!

    作为程序猿,谁不懂 if ! 在mybatis中也能用 if 啦:

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

    上面例子: 如果传入的id 不为空, 那么才会SQL才拼接id = #{id}。 这个相信大家看一样就能明白,不多说。细心的人会发现一个问题:“你这不对啊! 要是你传入的id为null, 那么你这最终的SQL语句不就成了 select * from user where and deleteFlag=0, 这语句有问题!”

    是啊,这时候,mybatis的 where 标签就该隆重登场啦。

    3 where, 有了我,SQL语句拼接条件神马的都是浮云!

    咱们通过where改造一下上面的例子:

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

    有些人就要问了: “你这都是些什么玩意儿! 跟上面的相比, 不就是多了个where标签嘛! 那这个还会不会出现 select * from user where and deleteFlag=0 ?”

    的确,从表面上来看,就是多了个where标签而已, 不过实质上, mybatis是对它做了处理,当它遇到AND或者OR这些,它知道怎么处理。其实我们可以通过 trim 标签去自定义这种处理规则。

    4 trim : 我的地盘,我做主!

    上面的where标签,其实用trim 可以表示如下:

    <trim prefix="WHERE" prefixOverrides="AND |OR ">
        ... 
    </trim>
    它的意思就是

    它的意思就是:当WHERE后紧随AND或则OR的时候,就去除AND或者OR。 除了WHERE以外,其实还有一个比较经典的实现,那就是SET。

    5 set: 信我,不出错!

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

    问题又来了: “如果我只有name不为null, 那么这SQL不就成了 update set name = #{name}, where ........ ? 你那name后面那逗号会导致出错啊!”

    是的,这时候,就可以用mybatis为我们提供的set 标签了。下面是通过set标签改造后:

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

    这个用trim 可表示为:

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

    WHERE是使用的 prefixOverrides(前缀), SET是使用的 suffixOverrides (后缀), 看明白了吧!

    6 foreach: 你有for, 我有foreach, 不要以为就你才屌!

    java中有for, 可通过for循环, 同样, mybatis中有foreach, 可通过它实现循环,循环的对象当然主要是java容器和数组。

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

    将一个 List 实例或者数组作为参数对象传给 MyBatis:当这么做的时候,MyBatis 会自动将它包装在一个 Map 中并以名称为键。List 实例将会以“list”作为键,而数组实例的键将是“array”。

    同样,当循环的对象为map的时候,index其实就是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>

    以上例子中:当title和author都不为null的时候, 那么选择二选一(前者优先), 如果都为null, 那么就选择 otherwise中的, 如果tilte和author只有一个不为null, 那么就选择不为null的那个。

    8 动态SQL解析原理

    我们在使用mybatis的时候,会在xml中编写sql语句。比如这段动态sql代码:

    1.  
      <update id="update" parameterType="org.format.dynamicproxy.mybatis.bean.User">
    2.  
      UPDATE users
    3.  
      <trim prefix="SET" prefixOverrides=",">
    4.  
      <if test="name != null and name != ''">
    5.  
      name = #{name}
    6.  
      </if>
    7.  
      <if test="age != null and age != ''">
    8.  
      , age = #{age}
    9.  
      </if>
    10.  
      <if test="birthday != null and birthday != ''">
    11.  
      , birthday = #{birthday}
    12.  
      </if>
    13.  
      </trim>
    14.  
      where id = ${id}
    15.  
      </update>

    mybatis底层是如何构造这段sql的?下面带着这个疑问,我们一步一步分析。

    8.1 关于动态SQL的接口和类

    1. SqlNode接口,简单理解就是xml中的每个标签,比如上述sql的update,trim,if标签:

      1.  
        public interface SqlNode {
      2.  
        boolean apply(DynamicContext context);
      3.  
        }

      SqlNode相关类图
    2. SqlSource Sql源接口,代表从xml文件或注解映射的sql内容,主要就是用于创建BoundSql,有实现类DynamicSqlSource(动态Sql源),StaticSqlSource(静态Sql源)等:

      1.  
        public interface SqlSource {
      2.  
        BoundSql getBoundSql(Object parameterObject);
      3.  
        }

      SqlSource相关类图
    3. BoundSql类,封装mybatis最终产生sql的类,包括sql语句,参数,参数源数据等参数:


      BoundSql类
    4. XNode,一个Dom API中的Node接口的扩展类:


      XNode类
    5. BaseBuilder接口及其实现类(属性,方法省略了,大家有兴趣的自己看),这些Builder的作用就是用于构造sql:


      BaseBuilder相关类图

      下面我们简单分析下其中4个Builder:

      XMLConfigBuilder:解析mybatis中configLocation属性中的全局xml文件,内部会使用XMLMapperBuilder解析各个xml文件。

      XMLMapperBuilder:遍历mybatis中mapperLocations属性中的xml文件中每个节点的Builder,比如user.xml,内部会使用XMLStatementBuilder处理xml中的每个节点。

      XMLStatementBuilder:解析xml文件中各个节点,比如select,insert,update,delete节点,内部会使用XMLScriptBuilder处理节点的sql部分,遍历产生的数据会丢到Configuration的mappedStatements中。

      XMLScriptBuilder:解析xml中各个节点sql部分的Builder。

    6. LanguageDriver接口及其实现类(属性,方法省略了,大家有兴趣的自己看),该接口主要的作用就是构造sql:


      LanguageDriver相关类图

      简单分析下XMLLanguageDriver(处理xml中的sql,RawLanguageDriver处理静态sql):XMLLanguageDriver内部会使用XMLScriptBuilder解析xml中的sql部分。

    8.2 源码分析走起

    Spring与Mybatis整合的时候需要配置SqlSessionFactoryBean,该配置会加入数据源和mybatis xml配置文件路径等信息:

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

    我们就分析这一段配置背后的细节:

    SqlSessionFactoryBean实现了Spring的InitializingBean接口,InitializingBean接口的afterPropertiesSet方法中会调用buildSqlSessionFactory方法,该方法内部会使用XMLConfigBuilder解析属性configLocation中配置的路径,还会使用XMLMapperBuilder属性解析mapperLocations属性中的各个xml文件。部分源码如下:

    1.  
      protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
    2.  
       
    3.  
      Configuration configuration;
    4.  
       
    5.  
      XMLConfigBuilder xmlConfigBuilder = null;
    6.  
      if (this.configLocation != null) {
    7.  
      // 1. 构建XMLConfigBuilder
    8.  
      xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
    9.  
      configuration = xmlConfigBuilder.getConfiguration();
    10.  
      } else {
    11.  
      if (logger.isDebugEnabled()) {
    12.  
      logger.debug("Property 'configLocation' not specified, using default MyBatis Configuration");
    13.  
      }
    14.  
      configuration = new Configuration();
    15.  
      configuration.setVariables(this.configurationProperties);
    16.  
      }
    17.  
       
    18.  
      if (this.objectFactory != null) {
    19.  
      configuration.setObjectFactory(this.objectFactory);
    20.  
      }
    21.  
       
    22.  
      if (this.objectWrapperFactory != null) {
    23.  
      configuration.setObjectWrapperFactory(this.objectWrapperFactory);
    24.  
      }
    25.  
       
    26.  
      if (hasLength(this.typeAliasesPackage)) {
    27.  
      String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage,
    28.  
      ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
    29.  
      for (String packageToScan : typeAliasPackageArray) {
    30.  
      configuration.getTypeAliasRegistry().registerAliases(packageToScan,
    31.  
      typeAliasesSuperType == null ? Object.class : typeAliasesSuperType);
    32.  
      if (logger.isDebugEnabled()) {
    33.  
      logger.debug("Scanned package: '" + packageToScan + "' for aliases");
    34.  
      }
    35.  
      }
    36.  
      }
    37.  
       
    38.  
      if (!isEmpty(this.typeAliases)) {
    39.  
      for (Class<?> typeAlias : this.typeAliases) {
    40.  
      configuration.getTypeAliasRegistry().registerAlias(typeAlias);
    41.  
      if (logger.isDebugEnabled()) {
    42.  
      logger.debug("Registered type alias: '" + typeAlias + "'");
    43.  
      }
    44.  
      }
    45.  
      }
    46.  
       
    47.  
      if (!isEmpty(this.plugins)) {
    48.  
      for (Interceptor plugin : this.plugins) {
    49.  
      configuration.addInterceptor(plugin);
    50.  
      if (logger.isDebugEnabled()) {
    51.  
      logger.debug("Registered plugin: '" + plugin + "'");
    52.  
      }
    53.  
      }
    54.  
      }
    55.  
       
    56.  
      if (hasLength(this.typeHandlersPackage)) {
    57.  
      String[] typeHandlersPackageArray = tokenizeToStringArray(this.typeHandlersPackage,
    58.  
      ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
    59.  
      for (String packageToScan : typeHandlersPackageArray) {
    60.  
      configuration.getTypeHandlerRegistry().register(packageToScan);
    61.  
      if (logger.isDebugEnabled()) {
    62.  
      logger.debug("Scanned package: '" + packageToScan + "' for type handlers");
    63.  
      }
    64.  
      }
    65.  
      }
    66.  
       
    67.  
      if (!isEmpty(this.typeHandlers)) {
    68.  
      for (TypeHandler<?> typeHandler : this.typeHandlers) {
    69.  
      configuration.getTypeHandlerRegistry().register(typeHandler);
    70.  
      if (logger.isDebugEnabled()) {
    71.  
      logger.debug("Registered type handler: '" + typeHandler + "'");
    72.  
      }
    73.  
      }
    74.  
      }
    75.  
       
    76.  
      if (xmlConfigBuilder != null) {
    77.  
      try {
    78.  
      // 2. 解析xmlConfigBuilder
    79.  
      xmlConfigBuilder.parse();
    80.  
       
    81.  
      if (logger.isDebugEnabled()) {
    82.  
      logger.debug("Parsed configuration file: '" + this.configLocation + "'");
    83.  
      }
    84.  
      } catch (Exception ex) {
    85.  
      throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
    86.  
      } finally {
    87.  
      ErrorContext.instance().reset();
    88.  
      }
    89.  
      }
    90.  
       
    91.  
      if (this.transactionFactory == null) {
    92.  
      this.transactionFactory = new SpringManagedTransactionFactory();
    93.  
      }
    94.  
       
    95.  
      Environment environment = new Environment(this.environment, this.transactionFactory, this.dataSource);
    96.  
      configuration.setEnvironment(environment);
    97.  
       
    98.  
      if (this.databaseIdProvider != null) {
    99.  
      try {
    100.  
      configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
    101.  
      } catch (SQLException e) {
    102.  
      throw new NestedIOException("Failed getting a databaseId", e);
    103.  
      }
    104.  
      }
    105.  
       
    106.  
      if (!isEmpty(this.mapperLocations)) {
    107.  
      for (Resource mapperLocation : this.mapperLocations) {
    108.  
      if (mapperLocation == null) {
    109.  
      continue;
    110.  
      }
    111.  
       
    112.  
      try {
    113.  
      // 3. 构建XMLMapperBuilder,并解析Mapper文件
    114.  
      XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
    115.  
      configuration, mapperLocation.toString(), configuration.getSqlFragments());
    116.  
      xmlMapperBuilder.parse();
    117.  
      } catch (Exception e) {
    118.  
      throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
    119.  
      } finally {
    120.  
      ErrorContext.instance().reset();
    121.  
      }
    122.  
       
    123.  
      if (logger.isDebugEnabled()) {
    124.  
      logger.debug("Parsed mapper file: '" + mapperLocation + "'");
    125.  
      }
    126.  
      }
    127.  
      } else {
    128.  
      if (logger.isDebugEnabled()) {
    129.  
      logger.debug("Property 'mapperLocations' was not specified or no matching resources found");
    130.  
      }
    131.  
      }
    132.  
       
    133.  
      return this.sqlSessionFactoryBuilder.build(configuration);
    134.  
      }

    再来看下,XMLConfigBudiler.parse()方法源码细节:

    1.  
      public Configuration parse() {
    2.  
      if (parsed) {
    3.  
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    4.  
      }
    5.  
      parsed = true;
    6.  
      parseConfiguration(parser.evalNode("/configuration"));
    7.  
      return configuration;
    8.  
      }
    9.  
       
    10.  
      private void parseConfiguration(XNode root) {
    11.  
      try {
    12.  
      propertiesElement(root.evalNode("properties")); //issue #117 read properties first
    13.  
      typeAliasesElement(root.evalNode("typeAliases"));
    14.  
      pluginElement(root.evalNode("plugins"));
    15.  
      objectFactoryElement(root.evalNode("objectFactory"));
    16.  
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
    17.  
      settingsElement(root.evalNode("settings"));
    18.  
      environmentsElement(root.evalNode("environments")); // read it after objectFactory and objectWrapperFactory issue #631
    19.  
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
    20.  
      typeHandlerElement(root.evalNode("typeHandlers"));
    21.  
      // 解析Mapper映射文件
    22.  
      mapperElement(root.evalNode("mappers"));
    23.  
      } catch (Exception e) {
    24.  
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    25.  
      }
    26.  
      }
    27.  
       
    28.  
      private void mapperElement(XNode parent) throws Exception {
    29.  
      if (parent != null) {
    30.  
      for (XNode child : parent.getChildren()) {
    31.  
      if ("package".equals(child.getName())) {
    32.  
      String mapperPackage = child.getStringAttribute("name");
    33.  
      configuration.addMappers(mapperPackage);
    34.  
      } else {
    35.  
      String resource = child.getStringAttribute("resource");
    36.  
      String url = child.getStringAttribute("url");
    37.  
      String mapperClass = child.getStringAttribute("class");
    38.  
      if (resource != null && url == null && mapperClass == null) {
    39.  
      ErrorContext.instance().resource(resource);
    40.  
      InputStream inputStream = Resources.getResourceAsStream(resource);
    41.  
      // 构建XMLMapperBuilder对象
    42.  
      XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
    43.  
      mapperParser.parse();
    44.  
      } else if (resource == null && url != null && mapperClass == null) {
    45.  
      ErrorContext.instance().resource(url);
    46.  
      InputStream inputStream = Resources.getUrlAsStream(url);
    47.  
      // 构建XMLMapperBuilder对象
    48.  
      XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
    49.  
      mapperParser.parse();
    50.  
      } else if (resource == null && url == null && mapperClass != null) {
    51.  
      Class<?> mapperInterface = Resources.classForName(mapperClass);
    52.  
      configuration.addMapper(mapperInterface);
    53.  
      } else {
    54.  
      throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
    55.  
      }
    56.  
      }
    57.  
      }
    58.  
      }
    59.  
      }

    由于XMLConfigBuilder内部也是使用XMLMapperBuilder,我们就看看XMLMapperBuilder的解析细节:


    XMLMapperBuilder.parse()源码

    XMLMapperBuilder.configurationElement()源码

    我们关注一下,增删改查节点的解析:


    增删改查节点的解析

    XMLStatementBuilder的解析:


    XMLStatementBuilder的解析

    默认会使用XMLLanguageDriver创建SqlSource(Configuration构造函数中设置)。

    XMLLanguageDriver创建SqlSource:


    XMLLanguageDriver创建SqlSource

    XMLScriptBuilder解析sql:


    XMLScriptBuilder解析sql

    得到SqlSource之后,会放到Configuration中,有了SqlSource,就能拿BoundSql了,BoundSql可以得到最终的sql。

    8.3 实例分析

    以下面的xml解析大概说下parseDynamicTags的解析过程:

    1.  
      <update id="update" parameterType="org.format.dynamicproxy.mybatis.bean.User">
    2.  
      UPDATE users
    3.  
      <trim prefix="SET" prefixOverrides=",">
    4.  
      <if test="name != null and name != ''">
    5.  
      name = #{name}
    6.  
      </if>
    7.  
      <if test="age != null and age != ''">
    8.  
      , age = #{age}
    9.  
      </if>
    10.  
      <if test="birthday != null and birthday != ''">
    11.  
      , birthday = #{birthday}
    12.  
      </if>
    13.  
      </trim>
    14.  
      where id = ${id}
    15.  
      </update>

    parseDynamicTags方法的返回值是一个List,也就是一个Sql节点集合。SqlNode本文一开始已经介绍,分析完解析过程之后会说一下各个SqlNode类型的作用。

    1. 首先根据update节点(Node)得到所有的子节点,分别是3个子节点:

      (1) 文本节点 UPDATE users;

      (2) trim子节点 ...;

      (3) 文本节点 where id = #{id};

    2. 遍历各个子节点:

      (1) 如果节点类型是文本或者CDATA,构造一个TextSqlNode或StaticTextSqlNode;

      (2) 如果节点类型是元素,说明该update节点是个动态sql,然后会使用NodeHandler处理各个类型的子节点。这里的NodeHandler是XMLScriptBuilder的一个内部接口,其实现类包括TrimHandler、WhereHandler、SetHandler、IfHandler、ChooseHandler等。看类名也就明白了这个Handler的作用,比如我们分析的trim节点,对应的是TrimHandler;if节点,对应的是IfHandler...这里子节点trim被TrimHandler处理,TrimHandler内部也使用parseDynamicTags方法解析节点。

    3. 遇到子节点是元素的话,重复以上步骤:

      trim子节点内部有7个子节点,分别是文本节点、if节点、是文本节点、if节点、是文本节点、if节点、文本节点。文本节点跟之前一样处理,if节点使用IfHandler处理。遍历步骤如上所示,下面我们看下几个Handler的实现细节。

      IfHandler处理方法也是使用parseDynamicTags方法,然后加上if标签必要的属性:

      1.  
        private class IfHandler implements NodeHandler {
      2.  
        public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
      3.  
        List<SqlNode> contents = parseDynamicTags(nodeToHandle);
      4.  
        MixedSqlNode mixedSqlNode = new MixedSqlNode(contents);
      5.  
        String test = nodeToHandle.getStringAttribute("test");
      6.  
        IfSqlNode ifSqlNode = new IfSqlNode(mixedSqlNode, test);
      7.  
        targetContents.add(ifSqlNode);
      8.  
        }
      9.  
        }

      TrimHandler处理方法也是使用parseDynamicTags方法,然后加上trim标签必要的属性:

      1.  
        private class TrimHandler implements NodeHandler {
      2.  
        public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
      3.  
        List<SqlNode> contents = parseDynamicTags(nodeToHandle);
      4.  
        MixedSqlNode mixedSqlNode = new MixedSqlNode(contents);
      5.  
        String prefix = nodeToHandle.getStringAttribute("prefix");
      6.  
        String prefixOverrides = nodeToHandle.getStringAttribute("prefixOverrides");
      7.  
        String suffix = nodeToHandle.getStringAttribute("suffix");
      8.  
        String suffixOverrides = nodeToHandle.getStringAttribute("suffixOverrides");
      9.  
        TrimSqlNode trim = new TrimSqlNode(configuration, mixedSqlNode, prefix, prefixOverrides, suffix, suffixOverrides);
      10.  
        targetContents.add(trim);
      11.  
        }
      12.  
        }
    4. 以上update方法最终通过parseDynamicTags方法得到的SqlNode集合如下:


      Paste_Image.png

      trim节点:


      Paste_Image.png

      由于这个update方法是个动态节点,因此构造出了DynamicSqlSource。DynamicSqlSource内部就可以构造sql了:


      Paste_Image.png

    DynamicSqlSource内部的SqlNode属性是一个MixedSqlNode。然后我们看看各个SqlNode实现类的apply方法。下面分析一下各个SqlNode实现类的apply方法实现:

      1. MixedSqlNode:MixedSqlNode会遍历调用内部各个sqlNode的apply方法。

        1.  
          public boolean apply(DynamicContext context) {
        2.  
          for (SqlNode sqlNode : contents) {
        3.  
          sqlNode.apply(context);
        4.  
          }
        5.  
          return true;
        6.  
          }
      2. StaticTextSqlNode:直接append sql文本。

        1.  
          public boolean apply(DynamicContext context) {
        2.  
          context.appendSql(text);
        3.  
          return true;
        4.  
          }
      3. IfSqlNode:这里的evaluator是一个ExpressionEvaluator类型的实例,内部使用了OGNL处理表达式逻辑。

        1.  
          public boolean apply(DynamicContext context) {
        2.  
          if (evaluator.evaluateBoolean(test, context.getBindings())) {
        3.  
          contents.apply(context);
        4.  
          return true;
        5.  
          }
        6.  
          return false;
        7.  
          }
      4. TrimSqlNode:

        1.  
          public boolean apply(DynamicContext context) {
        2.  
          FilteredDynamicContext filteredDynamicContext = new FilteredDynamicContext(context);
        3.  
          boolean result = contents.apply(filteredDynamicContext);
        4.  
          filteredDynamicContext.applyAll();
        5.  
          return result;
        6.  
          }
        7.  
           
        8.  
          public void applyAll() {
        9.  
          sqlBuffer = new StringBuilder(sqlBuffer.toString().trim());
        10.  
          String trimmedUppercaseSql = sqlBuffer.toString().toUpperCase(Locale.ENGLISH);
        11.  
          if (trimmedUppercaseSql.length() > 0) {
        12.  
          applyPrefix(sqlBuffer, trimmedUppercaseSql);
        13.  
          applySuffix(sqlBuffer, trimmedUppercaseSql);
        14.  
          }
        15.  
          delegate.appendSql(sqlBuffer.toString());
        16.  
          }
        17.  
           
        18.  
          private void applyPrefix(StringBuilder sql, String trimmedUppercaseSql) {
        19.  
          if (!prefixApplied) {
        20.  
          prefixApplied = true;
        21.  
          if (prefixesToOverride != null) {
        22.  
          for (String toRemove : prefixesToOverride) {
        23.  
          if (trimmedUppercaseSql.startsWith(toRemove)) {
        24.  
          sql.delete(0, toRemove.trim().length());
        25.  
          break;
        26.  
          }
        27.  
          }
        28.  
          }
        29.  
          if (prefix != null) {
        30.  
          sql.insert(0, " ");
        31.  
          sql.insert(0, prefix);
        32.  
          }
        33.  
          }
        34.  
          }

        TrimSqlNode的apply方法也是调用属性contents(一般都是MixedSqlNode)的apply方法,按照实例也就是7个SqlNode,都是StaticTextSqlNode和IfSqlNode。 最后会使用FilteredDynamicContext过滤掉prefix和suffix。

        https://blog.csdn.net/sdgihshdv/article/details/78258886

  • 相关阅读:
    c++中sort等算法中比较操作的规则
    数据结构(c++)(1)-- 栈
    Problem 10: Summation of primes
    Problem 9: Special Pythagorean triplet
    Problem 7: 10001st prime
    Problem 8: Largest product in a series
    Problem 5: Smallest multiple
    Problem 6: Sum square difference
    Problem 4: Largest palindrome product
    Problem 3: Largest prime factor
  • 原文地址:https://www.cnblogs.com/aaaazzzz/p/14303454.html
Copyright © 2011-2022 走看看