精华内容
下载资源
问答
  • 1.Mybatis动态sql做什么的?都有哪些动态sql?简述一下动态sql的执行原理? 1.动态SQL的概念 ​ 动态sql是指在进行sql操作的时候,传入的参数对象或者参数值,根据匹配的条件,有可能需要动态的去判断是否为空,...

    1.Mybatis动态sql是做什么的?都有哪些动态sql?简述一下动态sql的执行原理?

    1.动态SQL的概念

    ​ 动态sql是指在进行sql操作的时候,传入的参数对象或者参数值,根据匹配的条件,有可能需要动态的去判断是否为空,循环,拼接等情况;

    2.动态Sql的标签大致有以下几种
    if 和 where 标签和include标签

    ​ if标签中可以判断传入的值是否符合某种规则,比如是否不为空;

    ​ where标签可以用来做动态拼接查询条件,当和if标签配合的时候,不用显示的声明类似where 1=1这种无用的条件,来达到匹配的时候and会多余的情况;

    ​ include可以把大量重复的代码整理起来,当使用的时候直接include即可,减少重复代码的编写

    <!--动态Sql : where / if-->
    <select id="findUserById"  resultType="com.lagou.pojo.User">
      select <include refid="userInfo"/> from user
      <where>
        <if test="id != null and id != 0">
          AND id = #{id}
        </if>
        <if test="name != null and name != ''">
          AND name = #{name}
        </if>
      </where>
    </select>
    
    choose、when、otherwise 标签

    ​ 类似于 Java 中的 switch、case、default。只有一个条件生效,也就是只执行满足的条件 when,没有满足的条件就执行 otherwise,表示默认条件

    <!--动态Sql: choose、when、otherwise 标签-->
    <select id="findUserById" resultType="com.lagou.pojo.User">
      select * from user
      <where>
        <choose>
          <when test="name != null and name != ''">
            AND name = #{name}
          </when>
          <otherwise>
            AND id = #{id}
          </otherwise>
        </choose>
      </where>
    </select>
    

    foreach 标签

    ​ foreach标签可以把传入的集合对象进行遍历,然后把每一项的内容作为参数传到sql语句中,里面涉及到 item(具体的每一个对象), index(序号), open(开始符), close(结束符), separator(分隔符)

    <!--动态Sql: foreach标签, 批量插入-->
    <insert id="insertBatch" useGeneratedKeys="true" keyProperty="id">
      insert into user (id, name)
      values
      <foreach collection="list" item="user" separator="," >
        (#{user.id}, #{user.name})
      </foreach>
    </insert>
    
    <!--动态Sql: foreach标签, in查询-->
    <select id="dynamicSqlSelectList" resultType="com.lagou.pojo.User">
      SELECT * from user WHERE id in
      <foreach collection="list" item="id" open="(" close=")" separator="," >
        #{id}
      </foreach>
    </select>
    

    map参数

    ​ < map> 标签需要结合MyBatis的参数注解 @Param()来使用,需要告诉Mybatis配置文件中的collection="map"里的map是一个参数

    <!--动态Sql: foreach标签, map参数查询-->
    <select id="findByMap" resultType="com.lagou.pojo.User">
      select * from user WHERE
      <foreach collection="map" index="key" item="value"  separator="=">
        ${key} = #{value}
      </foreach>
    </select>
    

    set标签

    ​ 适用于更新中,当匹配某个条件后,才会对该字段进行更新操作

    <!--动态Sql: set 标签-->
    <update id="updateSet" parameterType="com.lagou.pojo.User">
      update user
      <set>
        <if test="name != null and name != ''">
          name = #{name},
        </if>
      </set>
      where id = #{id}
    </update>
    
    trim标签

    是一个格式化标签,主要有4个参数:

    prefix(前缀)

    prefixOverrides(去掉第一个标记)

    suffix(后缀)

    suffixOverrides(去掉最后一个标记)

    <!--动态Sql: trim 标签-->
    <select id="findUser" resultType="com.lagou.pojo.User">
      select * from user
      <trim prefix="where" suffix="order by id" prefixOverrides="and | or" suffixOverrides=",">
        <if test="name != null and name != ''">
          AND name = #{name}
        </if>
        <if test="id != null">
          AND id = #{id}
        </if>
      </trim>
    </select>
    
    3.动态sql的执行原理
    • 首先在解析xml配置文件的时候,会有一个SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass) 的操作
    • createSqlSource底层使用了XMLScriptBuilder来对xml中的标签进行解析
    • XMLScriptBuilder调用了parseScriptNode()的方法,
    • 在parseScriptNode()的方法中有一个parseDynamicTags()方法,会对nodeHandlers里的标签根据不同的handler来处理不同的标签
    • 然后把DynamicContext结果放回SqlSource中
    • DynamicSqlSource获取BoundSql
    • 在Executor执行的时候,调用DynamicSqlSource的解析方法,并返回解析好的BoundSql,和已经排好序,需要替换的参数
      在这里插入图片描述
      在这里插入图片描述

    ​ 简单的说:就是使用OGNL从sql参数对象中计算表达式的值,根据表达式的值动态拼接sql

    展开全文
  • Mybatis 动态 SQL ,可以让我们在 XML 映射文件内,以 XML 标签的形 式编写动态 SQL ,完成逻辑判断和动态拼接 SQL 的功能。 Mybatis 提供了 9 种动态 SQL 标签:<if/>、<choose/>、<when/>...

              Mybatis 动态 SQL ,可以让我们在 XML 映射文件内XML 标签的形 式编写动态 SQL 完成逻辑判断和动态拼接 SQL 的功能

           

              Mybatis 提供了 9 种动态 SQL 标签:<if/>、<choose/>、<when/>、<otherwise/>、<trim/>、<when/>、<set/>、<foreach/>、<bind/>。

           

    其执行原理为使用 OGNL 的表达式SQL 参数对象中计算表达式的值,根据表达式的值动态拼接 SQL以此来完成动态 SQL 的功能

    展开全文
  • 动态SQL

    万次阅读 多人点赞 2017-10-17 11:54:40
    那么,问题来了: 什么动态SQL? 动态SQL什么作用? 传统的使用JDBC的方法,相信大家在组合复杂的的SQL语句的时候,需要去拼接,稍不注意哪怕少了个空格,都会导致错误。Mybatis的动态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代码:

    <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底层是如何构造这段sql的?下面带着这个疑问,我们一步一步分析。

    8.1 关于动态SQL的接口和类

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

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

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

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

      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配置文件路径等信息:

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

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

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

      protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
    
          Configuration configuration;
    
          XMLConfigBuilder xmlConfigBuilder = null;
          if (this.configLocation != null) {
              // 1. 构建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. 构建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);
      }

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

      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);
                          // 构建XMLMapperBuilder对象
                          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);
                          // 构建XMLMapperBuilder对象
                          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内部也是使用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的解析过程:

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

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

      (1) 文本节点 \n UPDATE users;

      (2) trim子节点 ...;

      (3) 文本节点 \n 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标签必要的属性:

      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标签必要的属性:

      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);
         }
      }
    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方法。

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

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

      public boolean apply(DynamicContext context) {
         if (evaluator.evaluateBoolean(test, context.getBindings())) {
             contents.apply(context);
             return true;
         }
         return false;
      }
    4. 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方法也是调用属性contents(一般都是MixedSqlNode)的apply方法,按照实例也就是7个SqlNode,都是StaticTextSqlNode和IfSqlNode。 最后会使用FilteredDynamicContext过滤掉prefix和suffix。



    作者:陶邦仁
    链接:http://www.jianshu.com/p/e309ae5e4a77
    來源:简书
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
    展开全文
  • Java - MyBatis中的动态SQL什么意思?

    万次阅读 2019-03-20 11:30:36
    分享一个大牛的人工智能教程。零基础!通俗易懂!风趣幽默!希望你也加入到人工智能的队伍中来!... 对于一些复杂的查询,我们可能会指定多个查询条件,但是这些条件可能...此时就需要根据用户指定的条件动态生成SQL语...

    分享一个大牛的人工智能教程。零基础!通俗易懂!风趣幽默!希望你也加入到人工智能的队伍中来!请点击http://www.captainbed.net 

    对于一些复杂的查询,我们可能会指定多个查询条件,但是这些条件可能存在也可能不存在,例如在58同城上面找房子,我们可能会指定面积、楼层和所在位置来查找房源,也可能会指定面积、价格、户型和所在位置来查找房源,此时就需要根据用户指定的条件动态生成SQL语句。如果不使用持久层框架我们可能需要自己拼装SQL语句,还好MyBatis提供了动态SQL的功能来解决这个问题。MyBatis中用于实现动态SQL的元素主要有: 
    - if 
    - choose / when / otherwise 
    - trim 
    - where 
    - set 
    - foreach

    下面是映射文件的片段。

        <select id="foo" parameterType="Blog" resultType="Blog">
            select * from t_blog where 1 = 1
            <if test="title != null">
                and title = #{title}
            </if>
            <if test="content != null">
                and content = #{content}
            </if>
            <if test="owner != null">
                and owner = #{owner}
            </if>
        </select>

    当然也可以像下面这些书写。

        <select id="foo" parameterType="Blog" resultType="Blog">
            select * from t_blog where 1 = 1 
            <choose>
                <when test="title != null">
                    and title = #{title}
                </when>
                <when test="content != null">
                    and content = #{content}
                </when>
                <otherwise>
                    and owner = "owner1"
                </otherwise>
            </choose>
        </select>

    再看看下面这个例子。

        <select id="bar" resultType="Blog">
            select * from t_blog where id in
            <foreach collection="array" index="index" 
                item="item" open="(" separator="," close=")">
                #{item}
            </foreach>
        </select>
    展开全文
  • Mybatis 动态 sql 可以在 Xml 映射文件内,以标签的形式编写动态 sql,执行原理是根据表达式的值 完成逻辑判断并动态拼接 sql 的功能。 Mybatis 提供了 9 种动态 sql 标签:trim | where | set | foreach | if | ...
  • 详解mybatis动态SQL

    万次阅读 2020-09-10 09:43:19
    什么动态SQL? 动态 SQL 是 MyBatis 的强大特性之一。顾名思义,就是会动的SQL,即是能够灵活的根据某种条件拼接出完整的SQL语句。这种类似于MySQL中的case when then else then end....这种语法,能够根据某种...
  • SQLServer 执行动态SQL语句

    千次阅读 2019-09-22 17:04:38
    执行动态SQL语句 首先定义变量 @Games 为运动会名称, 为动态SQL语句定义变量 然后建立动态的SQL语句 最后运行这个动态的SQL语句 EXEC (@SQL2) 或 Exec SP_ExecuteSQL @SQL2 -- 执行动态SQL语句示例,复制后可直接...
  • MyBatis 动态 SQL

    千次阅读 2020-08-04 17:20:59
    MyBatis 令人喜欢的一大特性就是动态 SQL。在使用 JDBC 的过程中, 根据条件进行 SQL 的拼接是很麻烦且很容易出错的。MyBatis 动态 SQL 的出现, 解决了这个麻烦。 MyBatis通过 OGNL 来进行动态 SQL 的使用的。目前...
  • SQL Server 动态SQL拼接

    千次阅读 2019-03-31 22:54:38
    在多添加搜索数据时,大多会使用到动态SQL搜索,当搜索栏目中存在string,Guid,decimal等类型数据时,要注意拼接时数据类型转换,方法如下: 方法一:使用 SQL Server中的存储过程(StoredProcedure),在存储过程...
  • MyBatis 动态SQL

    千次阅读 热门讨论 2021-03-16 14:12:25
    MyBatis 动态SQL优点 MyBatis 的强大特性之一便是它的动态 SQL。如果你有使用 JDBC 或其他类似框架的经验,你就能体会到根据不同条件拼接 SQL 语句有多么痛苦,拼接的时候要确保不能忘了必要的空格,还要注意省掉...
  • Oracle 动态SQL

    千次阅读 2013-09-06 11:15:46
    Oracle 动态SQL 一、动态SQL的简介 1、定义 静态SQL是指直接嵌入到PL/SQL块中的SQL语句。 动态SQL是指运行PL/SQL块是动态输入的SQL语句。 2、适用范围 如果在PL/SQL块中需要执行DDL语句(create,alter,drop等)、DCL...
  • Mybatis 动态SQL之传字段名参数

    千次阅读 2018-09-21 16:09:00
    动态SQL是mybatis的强大特性之一,mybatis在对sql语句进行预编译之前,会对sql进行动态解析,解析为一个BoundSql对象,也是在此处对动态sql进行处理。   在动态sql解析过程,#{} 与 ${} 的效果是不一样的: 1. ...
  • T-SQL动态查询(4)——动态SQL

    万次阅读 2015-12-09 09:38:17
     为什么使用动态SQL: 在很多项目中,动态SQL被广泛使用甚至滥用,很多时候,动态SQL又确实是解决很多需求的首选方法。但是如果不合理地使用,会导致性能问题及无法维护。动态SQL尤其自己的优缺点,是否使用需要...
  • 动态SQL 和静态SQL 的 区别

    千次阅读 2018-08-20 11:24:57
    所谓SQL的静态和动态,是指SQL语句在何时被编译和执行,二者都是用在SQL嵌入式编程中的,这里所说的嵌入式是指将SQL语句嵌入在高级语言中,而不是针对于单片机的那种嵌入式编程...嵌入式动态SQL应用需要预编译,非嵌...
  • 动态SQL与静态SQL

    千次阅读 2012-06-14 22:18:49
    熟悉MS SQLServer的每一个人都应该意识到避免使用动态SQL的代价是较佳的。由于与静态SQL相比而言,动态SQL赋予了更多的权限。正因为你使用了动态SQL,所以会执行一些意想不到的代码。动态SQL通常需要较少的代码,与...
  • Mybatis动态SQL的实现

    万次阅读 多人点赞 2019-02-27 19:28:42
    Mybatis提供了动态SQL,也就是可以根据用户提供的参数,动态决定查询语句依赖的查询条件或SQL语句的内容。 动态SQL标签 if 和 where 标签 &amp;lt;!--动态Sql : where / if--&amp;gt; &amp;lt;select ...
  • MyBatis官方文档-动态 SQL

    千次阅读 2019-09-16 14:49:04
    动态 SQL MyBatis 的强大特性之一便是它的动态 SQL。如果你有使用 JDBC 或其它类似框架的经验,你就能体会到根据不同条件拼接 SQL 语句的痛苦。例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个...
  • MyBatis基础用法--动态SQL

    万次阅读 2017-10-05 19:07:34
    动态 SQL简介 if choose, when, otherwise trim, where, set foreach _parameter、_databaseId bind sql、include 动态 SQL简介 MyBatis 的强大特性之一便是它的动态 SQL。如果你有使用 JDBC 或其他类似...
  • Mybatis 动态SQL

    千次阅读 2012-04-09 21:50:40
    动态 SQL MyBatis 的一个强大的特性之一通常是它的动态 SQL 能力。 如果你有使用 JDBC 或其他 相似框架的经验,你就明白条件地串联 SQL 字符串在一起是多么的痛苦,确保不能忘了空 格或在列表的最后省略逗号。动态 SQL...
  • 动态sql语句

    千次阅读 2018-12-05 22:00:32
    1、什么动态sql?  mybatis 核心对sql语句进行灵活操作,通过表达式进行判断,对sql进行灵活拼接、组装。  首先要对查询条件进行判断,如果输入参数不为空才进行查询条件拼接。     实现的是sql的...
  • 动态SQL和动态PLSQL

    千次阅读 2016-09-09 21:31:26
    动态SQL 是指在执行时才构建 SQL 语句, 相对于静态 sql 的编译时就已经构建. 动态PLSQL 是指整个PL/SQL代码块都是动态构建, 然后再编译执行的. 作用: 1. 可以支持 DDL 语句, 静态 sql 只能支持 DML 语句. ...
  • MySQL-静态SQL 和动态SQL

    千次阅读 2016-10-28 10:37:42
    所谓SQL动态和静态,是指SQL语句在何时被编译和执行,二者都是用在SQL嵌入式编程中的。
  • 动态SQL拼装技巧

    千次阅读 2018-01-30 06:05:41
    存储过程里有时候需要根据不同的条件拼装动态SQL,比如下图是一个简单的sql语句拼装函数: 执行结果如下: PLSQL的字符串连接时,需要用2个单引号表示1个单引号,比如上例中的to_date函数里面用到的日期...
  • mybatis注解开发动态sql

    千次阅读 2019-06-03 19:06:35
    先来讲一下什么动态sql 在我们实际开发的时候可能会出现很多方法需要一条很相似的sql语句来进行增删改查,但是这几种方法又有不同的条件来执行,这时候我们就要对sql语句进行动态的更改,动态sql就是可以方便我们...
  • MyBatis动态SQL大全

    千次阅读 2018-03-21 13:48:45
    一、什么动态SQL MyBatis的动态SQL是基于OGNL的表达式的。它对SQL语句进行灵活的操作,通过表达式判断来实现对SQL的灵活拼接、组装。 二、动态SQL介绍 下面是用于实现动态SQL的主要元素: if choose...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 452,828
精华内容 181,131
关键字:

动态sql是做什么的