zoukankan      html  css  js  c++  java
  • mybatis问题合集:#{}与${}区别、动态sql语句、缓存机制

    一、MyBatis 中#{}和${}区别

      #{} 是预编译处理,像传进来的数据会加个" "(#将传入的数据都当成一个字符串,会对自动传入的数据加一个双引号)

      ${} 就是字符串替换。直接替换掉占位符。$方式一般用于传入数据库对象,例如传入表名.

      使用 ${} 的话会导致 sql 注入。什么是 SQL 注入呢?比如 select * from user where id = ${value}。value 应该是一个数值吧。然后如果对方传过来的是 001  and name = tom。这样不就相当于多加了一个条件嘛?把SQL语句直接写进来了。如果是攻击性的语句呢?001;drop table user,直接把表给删了。

      所以为了防止 SQL 注入,能用 #{} 的不要去用 ${}

      如果非要用 ${} 的话,那要注意防止 SQL 注入问题,可以手动判定传入的变量,进行过滤,一般 SQL 注入会输入很长的一条 SQL 语句

    二、动态sql语句

    1、if:动态 SQL 通常要做的事情是有条件地包含 where 子句的一部分。

    <select id="select" resultType="Blog">
      SELECT * FROM BLOG
      WHERE state = ‘ACTIVE’
      <if test="title != null">
        AND title like #{title}
      </if>
     <if test="name!= null">
        AND name like #{title}
      </if>
    </select>

    2、where:像上面的那种情况,如果where后面没有条件,然后需要直接写if判断(开头如果是 and / or 的话,会去除掉)

    <select id="select" resultType="Blog">
      SELECT * FROM BLOG
      <where>
          <if test="title != null">
            AND title like #{title}
          </if>
          <if test="name!= null">
            AND name like #{title}
          </if>
      <where>
    </select>

    3、choose(when、otherwise):choose 相当于 java 里面的 switch 语句,otherwise(其他情况)

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

    4、set:set 元素主要是用在更新操作的时候,如果包含的语句是以逗号结束的话将会把该逗号忽略,如果set包含的内容为空的话则会出错。

    <update id="dynamicSetTest" parameterType="Blog">  
        update t_blog  
        <set>  
            <if test="title != null">  
                title = #{title},  
            </if>  
            <if test="content != null">  
                content = #{content},  
            </if>  
            <if test="owner != null">  
                owner = #{owner}  
            </if>  
        </set>  
        where id = #{id}  
    </update> 

    5、foreach:主要用在构建in条件中

      foreach 元素的功能是非常强大的,它允许你指定一个集合,声明可以用在元素体内的集合项和索引变量。它也允许你指定开闭匹配的字符串以及在迭代中间放置分隔符。这个元素是很智能的,因此它不会偶然地附加多余的分隔符。

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

    <select id="dynamicForeachTest" resultType="Blog">  
      select * from t_blog where id in  
      <foreach collection="list" index="index" item="item" open="(" separator="," close=")">  
        #{item}  
      </foreach>
    </select>  

      open separator close 相当于是 in (?,?,?)

      如果是个map怎么办

    <select id="dynamicForeach3Test" resultType="Blog">  
      select * from t_blog where title like "%"#{title}"%" and id in  
      <foreach collection="ids" index="index" item="item" open="(" separator="," close=")">  
        #{item}  
      </foreach>  
    </select>  
    // collection对应map的键,像这样
    List<Integer> ids = new ArrayList<Integer>();  
    ids.add(1);
    ids.add(2);
    ids.add(3);
    Map<String, Object> params = new HashMap<String, Object>();  
    params.put("ids", ids);  

    6、trim、where、set解决的问题

      前面几个例子已经合宜地解决了一个臭名昭著的动态 SQL 问题。现在考虑回到"if"示例,这次我们将"ACTIVE = 1"也设置成动态的条件,看看会发生什么。看看设计 trim、where、set 所解决的问题。

    <select id="findActiveBlogLike" resultType="Blog">
      SELECT * FROM BLOG 
      WHERE 
      <if test="state != null">
        state = #{state}
      </if> 
      <if test="title != null">
        AND title like #{title}
      </if>
      <if test="author != null and author.name != null">
        AND author_name like #{author.name}
      </if>
    </select>

      如果这些条件没有一个能匹配上将会怎样?最终这条 SQL 会变成这样:SELECT * FROM BLOG WHERE

      这会导致查询失败。如果仅仅第二个条件匹配又会怎样?这条 SQL 最终会是这样:SELECT * FROM BLOG WHERE AND title like 'someTitle'

      MyBatis 有一个简单的处理,这在90%的情况下都会有用。而在不能使用的地方,你可以自定义处理方式来令其正常工作。一处简单的修改就能得到想要的效果。

      where 元素知道只有在一个以上的if条件有值的情况下才去插入"WHERE"子句。而且,若最后的内容是"AND"或"OR"开头的,where 元素也知道如何将他们去除。

      如果 where 元素没有按正常套路出牌,我们还是可以通过自定义 trim 元素来定制我们想要的功能。比如,和 where 元素等价的自定义 trim 元素为:trim标记是一个格式化的标记,可以完成set或者是where标记的功能,

    // prefix:前缀   prefixoverride:去掉第一个and或者是or
    select * from test
    <trim prefix="WHERE" prefixoverride="AND丨OR">
          <if test="a!=null and a!=' '">AND a=#{a}<if>
          <if test="b!=null and b!=' '">AND a=#{a}<if>
    </trim>

      类似的用于动态更新语句的解决方案叫做 set。set 元素可以被用于动态包含需要更新的列,而舍去其他的。比如:

    <update id="updateAuthorIfNecessary">
      update Author
        <set>
          <if test="username != null">username=#{username},</if>
          <if test="password != null">password=#{password},</if>
          <if test="email != null">email=#{email},</if>
          <if test="bio != null">bio=#{bio}</if>
        </set>
      where id=#{id}
    </update>

      这里,set 元素会动态前置 SET 关键字,同时也会消除无关的逗号,因为用了条件语句之后很可能就会在生成的赋值语句的后面留下这些逗号。

      若你对等价的自定义 trim 元素的样子感兴趣,那这就应该是它的真面目:

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

    三、缓存机制

      缓存机制减轻数据库压力,提高数据库性能。mybatis的缓存分为两级:一级缓存、二级缓存

    1、一级缓存:

      一级缓存为 sqlsesson 缓存,缓存的数据只在 SqlSession 内有效。在操作数据库的时候需要先创建 SqlSession 会话对象,在对象中有一个 HashMap 用于存储缓存数据,此 HashMap 是当前会话对象私有的,别的 SqlSession 会话对象无法访问。

    具体流程:

        第一次执行 select 完毕会将查到的数据写入 SqlSession 内的 HashMap 中缓存起来

        第二次执行 select 会从缓存中查数据,如果 select 相同切传参数一样,那么就能从缓存中返回数据,不用去数据库了,从而提高了效率

    注意:

        1、如果 SqlSession 执行了 DML 操作(insert、update、delete),并 commit 了,那么 mybatis 就会清空当前 SqlSession 缓存中的所有缓存数据,这样可以保证缓存中的存的数据永远和数据库中一致,避免出现脏读

       2、当一个 SqlSession 结束后那么他里面的一级缓存也就不存在了, mybatis 默认是开启一级缓存,不需要配置

       3、 mybatis 的缓存是基于 [namespace:sql语句:参数] 来进行缓存的,意思就是, SqlSession 的 HashMap 存储缓存数据时,是使用 [namespace:sql:参数] 作为 key ,查询返回的语句作为 value 保存的

    2、二级缓存:

      二级缓存是 mapper 级别的缓存,也就是同一个 namespace 的 mappe.xml ,当多个 SqlSession 使用同一个 Mapper 操作数据库的时候,得到的数据会缓存在同一个二级缓存区域

      二级缓存默认是没有开启的。需要在 setting 全局参数中配置开启二级缓存

      开启二级缓存步骤:

      1、conf.xml 配置全局变量开启二级缓存

    <settings>
        <setting name="cacheEnabled" value="true"/> // 默认是false:关闭二级缓存
    <settings>

      2、在userMapper.xml中配置

    <cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/>
    // 当前mapper下所有语句开启二级缓存
    
    // 这里配置了一个LRU缓存,并每隔60秒刷新,最大存储512个对象,而且返回的对象是只读的
    
    // 若想禁用当前select语句的二级缓存,添加useCache="false"修改如下:
    <select id="getCountByName" parameterType="java.util.Map" resultType="INTEGER" statementType="CALLABLE" useCache="false">

    具体流程:

      1.当一个 sqlseesion 执行了一次 select 后,在关闭此 session 的时候,会将查询结果缓存到二级缓存

      2.当另一个 sqlsession 执行 select 时,首先会在他自己的一级缓存中找,如果没找到,就回去二级缓存中找,找到了就返回,就不用去数据库了,从而减少了数据库压力提高了性能。

  • 相关阅读:
    XPath中的text()和string()区别(转)
    (转)Ubuntu 16.04 安裝Docker(PS:本文适用amd64位的ubuntu系统)
    python 爬取世纪佳缘,经过js渲染过的网页的爬取
    Python中的join()函数的用法
    Ubuntu下修改ubuntu源,完成Redis Desktop Manager的安装
    Ubuntu16.04安装Redis
    Scrapy爬虫实例教程(二)---数据存入MySQL
    Ubuntu16.04安装mongodb 及使用
    关闭和启动网卡
    网络之端口的作用
  • 原文地址:https://www.cnblogs.com/goloving/p/13669726.html
Copyright © 2011-2022 走看看