1. 程式人生 > 實用技巧 >動態SQL(Foreach)、關於快取

動態SQL(Foreach)、關於快取

SQL片段(20-12-24)

有的時候,我們可能會將一些功能的部分抽取出來,方便複用!

  1. 使用SQL標籤抽取公共部分

    <sql id="if-title-author">
        <if test="title != null">
            title = #{title}
        </if>
        <if test="author != null">
            and author = #{author}
        </if>
    </sql>
    
  2. 在需要使用的地方使用Include標籤引用即可

    <select id="queryBlogIF" parameterType="map" resultType="blog">
        select * from mybatis.blog
        #         where 1=1
        <where>
            <include refid="if-title-author"></include>
        </where>
    </select>
    

注意事項:

  • 最好基於單表來定義SQL片段!
  • 不要存在where標籤

Foreach

select * from user where 1=1 and

  <foreach item="id" collection="ids"
      open="(" separator="," close=")">
        #{id}
  </foreach>
  
(id=1 or id=2 or id=3)

<!--
        select * from mybatis.blog where 1=1 and (id=1 or id=2 or id=3)

        我們現在傳遞一個萬能的map,這map中可以存在一個集合!
-->
<select id="queryBlogForeach" parameterType="map" resultType="blog">
    select * from mybatis.blog
    <where>
        <foreach collection="ids" item="id" open="and (" close=")" separator="or">
            id=#{id}
        </foreach>
    </where>
</select>

動態SQL就是在拼接SQL語句,我們只要保證SQL的正確性,按照SQL的格式,去排列組合就可以了

建議:

  • 先在Mysql中寫出完整的SQL,再對應的去修改成為我們的動態SQL實現通用即可!

13、快取

13.1、簡介

  1. 什麼是快取[Cache]?
    • 存在記憶體中的臨時資料。
    • 將使用者經常查詢的資料放在快取中(記憶體)中,使用者去查詢資料就不用從磁碟上(關係型資料庫資料檔案)查詢,從快取中查詢,從而提高查詢效率,解決了高併發系統的效能問題。
  2. 為什麼使用快取?
    • 減少和資料庫的互動次數,減少系統開銷,提高系統效率。
  3. 什麼樣的資料能使用快取?
    • 經常查詢並且不經常改變大資料。

13.2、Mybatis快取

  • MyBatis包含一個非常強大的查詢快取特性,它可以非常方便地定製和配置快取。快取可以極大的提升查詢效率。
  • MyBatis系統中預設定義了兩級快取:一級快取二級快取
    • 預設情況下,只有一級快取開啟。(SqlSession級別的快取,也稱為本地快取)
    • 二級快取需要手動開啟和配置,它是基於namespace級別的快取。
    • 為了提高擴充套件性,MyBatis定義了快取介面Cache。我們可以通過實現Cache介面來自定義二級快取。

13.3、一級快取

  • 一級快取也叫本地快取:SqlSession
    • 與資料庫同一次會話期間查詢到的資料會放在本地快取中。
    • 以後如果需要獲取相同的資料,直接從快取中拿,沒必要再去查詢資料庫。

測試步驟:

  1. 開啟日誌

  2. 測試在一個SqlSession中查詢兩次相同的記錄

  3. 檢視日誌輸出

    Opening JDBC Connection
    Created connection 811760110.
    ==>  Preparing: select * from mybatis.user where id = ? 
    ==> Parameters: 1(Integer)
    <==    Columns: id, name, pwd
    <==        Row: 1, 狂神, 123456
    <==      Total: 1
    User{id=1, name='狂神', password='123456'}
    ===================
    User{id=1, name='狂神', password='123456'}
    Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@306279ee]
    Returned connection 811760110 to pool.
    

快取失效的情況:

  1. 查詢不同的東西

  2. 增刪改操作,可能會改變原來的資料,所以必定會重新整理快取!

            User user = mapper.queryUserById(1);
            System.out.println(user);
            mapper.updateUser(new User(2,"aaaa","bbbbb"));     
            System.out.println("===================");
            User user2 = mapper.queryUserById(1);
            System.out.println(user2);
    
  3. 查詢不同的Mapper.xml

  4. 手動清理快取

            User user = mapper.queryUserById(1);
            System.out.println(user);
            //mapper.updateUser(new User(2,"aaaa","bbbbb"));
            sqlSession.clearCache();
            System.out.println("===================");
            User user2 = mapper.queryUserById(1);
            System.out.println(user2);
    

小結:一級快取預設是開啟的,只在一次SqlSession中有效,也就是拿到連線到關閉連線這個區間段!

一級快取就是一個Map (集合)

13.4、二級快取

  • 二級快取也叫全域性快取,一級快取作用域太低,所以誕生了二級快取
  • 基於namespace級別的快取,一個名稱空間,對應一個二級快取。
  • 工作機制
    • 一個會話查詢一條資料,這個資料就會被放在當前會話的一級快取中。
    • 如果當前會話關閉了,這個會話對應的一級快取就沒了;但是我們想要的是,會話關閉了,一級快取中的資料被儲存到二級快取中了。
    • 新的會話查詢資訊,就可以從二級快取中獲取內容。
    • 不同的mapper查出的資料會放在自己對應的快取(map)中。

步驟:

  1. 開啟全域性快取

    <!--顯示的開啟全域性快取-->
    <setting name="cacheEnabled" value="true"/>
    
  2. 在要使用二級快取的Mapper中開啟

    <!--在當前mapper.xml中開啟二級快取-->
    <cache/>
    

    也可以自定義引數

    <!--在當前mapper.xml中開啟二級快取-->
    <cache
           eviction="FIFO"
           flushInterval="60000"
           size="512"
           readOnly="true"/>
    
  3. 測試

    SqlSession sqlSession = MybatisUtils.getSqlSession();
    SqlSession sqlSession2 = MybatisUtils.getSqlSession();
    
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
    
    User user = mapper.queryUserById(1);
    System.out.println(user);
    sqlSession.close();
    
    User user2 = mapper2.queryUserById(1);
    System.out.println(user2);
    
    
    System.out.println(user==user2);
    sqlSession2.close();
    
    /*
    result
    Opening JDBC Connection
    Created connection 1434041222.
    ==>  Preparing: select * from mybatis.user where id = ? 
    ==> Parameters: 1(Integer)
    <==    Columns: id, name, pwd
    <==        Row: 1, 狂神, 123456
    <==      Total: 1
    User{id=1, name='狂神', password='123456'}
    Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@5579bb86]
    Returned connection 1434041222 to pool.
    Cache Hit Ratio [com.kuang.dao.UserMapper]: 0.5
    User{id=1, name='狂神', password='123456'}
    true
    */
    
    1. 問題:我們需要將實體類序列化!否則報錯!

      Cause by: java.io.NotSerializableException: com.kuang.pojo.User
      

小結:

  • 只要開啟了二級快取,在同一個Mapper下就有效
  • 所有的資料都會先放在一級快取中
  • 只有當回話提交,或者關閉時,才會提交到二級快取中!

13.5、快取原理

13.6、自定義快取-ehcache

Ehcache是一種廣泛使用的Java分散式快取。主要面向通用快取

要在程式中使用ehcache,先要導包!

<!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache -->
<dependency>
    <groupId>org.mybatis.caches</groupId>
    <artifactId>mybatis-ehcache</artifactId>
    <version>1.1.0</version>
</dependency>

在mapper中指定使用我們的ehcache快取實現!

<!--在當前mapper.xml中開啟二級快取-->
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>

ehcache.xml

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
         updateCheck="false">
    <!--
      diskStore:為快取路徑,ehcache分為記憶體和磁碟兩級,此屬性定義磁碟的快取位置。引數解釋如下:
      user.home – 使用者主目錄
      user.dir – 使用者當前工作目錄
      java.io.tmpdir – 預設臨時檔案路徑
    -->
    <diskStore path="./tmpdir/Tmp_EhCache"/>

    <defaultCache
                  eternal="false"
                  maxElementsInMemory="10000"
                  overflowToDisk="false"
                  diskPersistent="false"
                  timeToIdleSeconds="1800"
                  timeToLiveSeconds="259200"
                  memoryStoreEvictionPolicy="LRU"/>

    <cache
           name="cloud_user"
           eternal="false"
           maxElementsInMemory="5000"
           overflowToDisk="false"
           diskPersistent="false"
           timeToIdleSeconds="1800"
           timeToLiveSeconds="1800"
           memoryStoreEvictionPolicy="LRU"/>
    <!--
      defaultCache:預設快取策略,當ehcache找不到定義的快取時,則使用這個快取策略。只能定義一個。
    -->
    <!--
     name:快取名稱。
     maxElementsInMemory:快取最大數目
     maxElementsOnDisk:硬碟最大快取個數。
     eternal:物件是否永久有效,一但設定了,timeout將不起作用。
     overflowToDisk:是否儲存到磁碟,當系統當機時
     timeToIdleSeconds:設定物件在失效前的允許閒置時間(單位:秒)。僅當eternal=false物件不是永久有效時使用,可選屬性,預設值是0,也就是可閒置時間無窮大。
     timeToLiveSeconds:設定物件在失效前允許存活時間(單位:秒)。最大時間介於建立時間和失效時間之間。僅當eternal=false物件不是永久有效時使用,預設是0.,也就是物件存活時間無窮大。
     diskPersistent:是否快取虛擬機器重啟期資料 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
     diskSpoolBufferSizeMB:這個引數設定DiskStore(磁碟快取)的快取區大小。預設是30MB。每個Cache都應該有自己的一個緩衝區。
     diskExpiryThreadIntervalSeconds:磁碟失效執行緒執行時間間隔,預設是120秒。
     memoryStoreEvictionPolicy:當達到maxElementsInMemory限制時,Ehcache將會根據指定的策略去清理記憶體。預設策略是LRU(最近最少使用)。你可以設定為FIFO(先進先出)或是LFU(較少使用)。
     clearOnFlush:記憶體數量最大時是否清除。
     memoryStoreEvictionPolicy:可選策略有:LRU(最近最少使用,預設策略)、FIFO(先進先出)、LFU(最少訪問次數)。
     FIFO,first in first out,這個是大家最熟的,先進先出。
     LFU, Less Frequently Used,就是上面例子中使用的策略,直白一點就是講一直以來最少被使用的。如上面所講,快取的元素有一個hit屬性,hit值最小的將會被清出快取。
     LRU,Least Recently Used,最近最少使用的,快取的元素有一個時間戳,當快取容量滿了,而又需要騰出地方來快取新的元素的時候,那麼現有快取元素中時間戳離當前時間最遠的元素將被清出快取。
  -->

</ehcache>

以後大多數用Redis資料庫來做快取! K-V