MyBatis緩存機制(一級緩存,二級緩存)

一,MyBatis一級緩存(本地緩存)java

   My Batis 一級緩存存在於 SqlSession 的生命週期中,是SqlSession級別的緩存。在操做數據庫時須要構造SqlSession對象,在對象中有一個數據結構用來存儲緩存數據。不一樣的SqlSession之間的數據緩存是不能共享的。
  在同一個SqlSession 中查詢數據時,sqlSession會先在一級緩存中查找,若是有,直接讀取,若是沒有,則從數據庫中查詢, 接着把執行的方法和參數經過算法生成緩存的鍵值,將鍵值和查詢結果存入一級緩存中(以Map對象的形式)。若是後面再次執行相同方法,SqlSession經過算法會生成相同的鍵值,而後在一級緩存中查找,因爲一級緩存中己經存在該鍵值,因此會返回緩存中的對象。與執行select不一樣的是,執行update,insert,delect操做後會清空一級緩存中的數據,而不是經過算法生成緩存的鍵值存入一級緩存,之因此有這種差異是由於 select的flushCache(清空緩存)默認爲false,而update,insert,delect的flushCache(清空緩存)默認爲true。
  固然也可使用下面的方法對select操做進行設置,
<select id="selectStudentByIdAndName"  flushCache=」true  resultType="student">
    select * from student where sid=#{Sid} and s_name=#{Sname}
</select
  就是在原來方法的基礎上增長了 flushCache= true ,這個屬性配置爲 true 後,在查詢數據後會清空當前的一級緩存,所以調用該方法後每次都會從新從數據庫中查詢數據,可是因爲這個方法清空了一級緩存,會影響當前 SqlSession 中全部緩存的查詢,所以在須要反覆查詢獲取只讀數據的狀況下,會增長數據庫的查詢次數,因此要避免這麼使用。
  除了上面講的將 flushCache賦值爲true的狀況外,還會致使一級緩存清空的狀況就是關閉第一個 SqlSession,而後從新開啓一個SqlSession,因爲一級緩存是和 SqlSession 綁定的,只存在於 SqlSession的生命週期中,因此在新的SqlSession中調用剛纔的方法,在緩存中就查不到,必須去數據庫中查詢,固然以後在調用過該方法並不清除的狀況下就能夠在緩存中取到了。
一級緩存原理圖:
代碼:
public class MyBatisTest {
    public static void main( String[] args ) {
        SqlSession openSession = null;
        try {
            //mybatis配置文件
            String resourse="mybatis-cfg.xml";
            //經過 Resources 工具類將 ti -config.xm 配置文件讀入 Reader
            InputStream inputStream=Resources.getResourceAsStream(resourse);
            //經過 SqlSessionFactoryBuilder 建造類使用 Reader 建立 SqlSessionFactory工廠對象
            SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(inputStream);
            //經過SqlSessionFactory工廠獲得SqlSession
            openSession = sqlSessionFactory.openSession();
            //經過反射機制來獲取對應的Mapper實例
            StudentMapper mapper=openSession.getMapper(StudentMapper.class);
            Student student1=mapper.selectStudentByIdAndName(2,"danghh");
            Student student2=mapper.selectStudentByIdAndName(2,"danghh");
            System.out.println(student1);
            openSession.commit();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            //最後必定不要忘記關閉 SqlSession ,不然會由於鏈接沒有關閉致使數據庫鏈接數過多,形成系統崩旗
            openSession.close();
        }
    }
}
運行結果: 
[DEBUG] - Setting autocommit to false on JDBC Connection[com.mysql.jdbc.JDBC4Connection@dfd3711]
[DEBUG] - ==>  Preparing: select * from student where sid=? and s_name=? 
[DEBUG] - ==> Parameters: 2(Integer), danghh(String)
[DEBUG] - <==      Total: 1
 Student{SID=2, Sname='danghh', Sage=22, Ssex='nv', course=null}
Student{SID=2, Sname='danghh', Sage=22, Ssex='nv', course=null}

  經過結果能夠看出,因爲代碼中查詢是在一個SqlSession,且兩次查詢過程當中沒有更新信息,不會致使一級緩存失效,因此結果只進行了一次數據庫查詢。mysql

  那若是是在兩個SqlSession中分別進行查詢呢?算法

 代碼:

 結果:sql

[DEBUG] - Opening JDBC Connection
[DEBUG] - Checked out connection 234698513 from pool.
[DEBUG] - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@dfd3711]
[DEBUG] - ==>  Preparing: select * from student where sid=? and s_name=? 
[DEBUG] - ==> Parameters: 2(Integer), danghh(String)
[DEBUG] - <==      Total: 1
[DEBUG] - Opening JDBC Connection
[DEBUG] - Created connection 1836797772.
[DEBUG] - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@6d7b4f4c]
[DEBUG] - ==>  Preparing: select * from student where sid=? and s_name=? 
[DEBUG] - ==> Parameters: 2(Integer), danghh(String)
[DEBUG] - <==      Total: 1
Student{SID=2, Sname='danghh', Sage=22, Ssex='nv', course=null}
Student{SID=2, Sname='danghh', Sage=22, Ssex='nv', course=null}
   能夠看出在使用了兩個SqlSession進行查詢後,在數據庫進行了兩次查詢,也驗證了SqlSession的一級緩存是和SqlSession的生命週期綁定的,做用範圍也只在當前SqlSession中。
  另外在演示下若是在進行查詢以前進行了一次update操做會不會使一級緩存清空呢?
代碼:
運行結果:
 [DEBUG] - ==>  Preparing: select * from student where sid=? and s_name=? 
 [DEBUG] - ==> Parameters: 2(Integer), hjj(String)
 [DEBUG] - <==      Total: 1
 [DEBUG] - ==>  Preparing: update student set S_name=?,Sage=?,Ssex=? where Sid=? 
 [DEBUG] - ==> Parameters: hjj(String), 23(Integer), null, 2(Integer)
 [DEBUG] - <==    Updates: 1
 [DEBUG] - ==>  Preparing: select * from student where sid=? and s_name=? 
 [DEBUG] - ==> Parameters: 2(Integer), hjj(String)
 [DEBUG] - <==      Total: 1
  Student{SID=2, Sname='hjj', Sage=23, Ssex='null', course=null}
  Student{SID=2, Sname='hjj', Sage=23, Ssex='null', course=null}
二,MyBatis二級緩存(全局緩存)
  MyBatis二級緩存很是強大,它不一樣於一級緩存只存在於 SqlSession 的生命週期中, 而是能夠理解爲存在於 SqlSessionFactory 的生命週期中 ,是Mapper(studentMapper) 級別的緩存,一個Mapper對應一個二級緩存,當Mapper中的多個SqlSession共同操做同一個方法時,多個SqlSession是能夠共用二級緩存的中的數據的,因此二級緩存是跨SqlSession的。
  在開啓二級緩存時,查出來的數據默認先存儲在一級緩存中,當有SqlSession關閉時,它裏面一級緩存中的數據就會被存儲到Mapper的二級緩存中,這樣該Mapper中的其餘會話執行了相同方法時,就會在二級緩存中找到匹配的數據,若是沒有找到,纔會去數據庫中查找。注意只有在該會話關閉時,它一級緩存中的數據纔會被刷到二級緩存中。另外若是隻是開啓二級緩存的全局(config)開關,而會話(student)沒有開啓二級緩存,查詢時也不會在二級緩存中查詢。
  一級緩存( 也叫本地緩存)通常默認會啓開,不須要進行配置,但要使用二級緩存就須要進行配置。 那如何配置呢?
第一步:在全局配置文件中添加

(這個參數是二級緩存的全局開關,默認值是 true ,初始狀態爲啓用狀態,因此也可忽略此步的配置)數據庫

  

(因爲MyBatis二級緩存和命名空間namespace是綁定的 ,即二級緩存還須要在 Mapper.xml 映射文件中配置或者在 Mapper.java 接口中配置。)緩存

第二步:在Sql映射文件中添加<cache></cache>元素。
        
  上面的配置建立了一個 FIFO 緩存,並每隔6秒刷新一次,存儲集合或對象的1024個引用,並且返回的對象被認爲是非只讀的。
  eviction :緩存的收回策略
  • LRU (最近最少使用的) 移除最長時間不被使用的對象,這是默認值
  • FIFO (先進先出〉 按對象進入緩存的順序來移除它們
  • SOFT (軟引用) 移除基於垃圾回收器狀態和軟引用規則的對象
  • WEAK (弱引用) 更積極地移除基於垃圾收集器狀態和弱引用規則的對象
  flushinterval :刷新間隔
    設置緩存多長時間清空一次,單位爲毫秒值,默認不清空。
  readOnly:是否只讀
    true:只讀,設置爲true後,mybatis認爲全部從緩存中獲取數據的操做都是隻讀操做,不會修改數據,所以爲了加快獲取速度,通常會直接將數據在緩存中的引用交給用戶,雖然速度快,但不安全。
    false:非只讀,設置爲false後,mybatis認爲獲取的數據可能會被修改,所以會利用序列化和反序列化的技術克隆一份新的數據給你,雖然速度慢,但安全。
       默認是 false
  size :引用數目
    設置緩存能夠存放的引用數目,能夠被設置爲任意正整數 。默認值是 1024。
第三步:給POJO類實現序列化接口
  

代碼:安全

public class MyBatisTest {
    public static void main( String[] args ) {
        SqlSession openSession1 = null;
        SqlSession openSession2 = null;
        try {
            //mybatis配置文件
            String resourse="mybatis-cfg.xml";
            //經過 Resources 工具類將 ti -config.xm 配置文件讀入 Reader
            InputStream inputStream=Resources.getResourceAsStream(resourse);
            //經過 SqlSessionFactoryBuilder 建造類使用 Reader 建立 SqlSessionFactory工廠對象
            SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(inputStream);
            //經過SqlSessionFactory工廠獲得SqlSession1
            openSession1 = sqlSessionFactory.openSession();
            StudentMapper mapper1=openSession1.getMapper(StudentMapper.class);
            //經過SqlSessionFactory工廠獲得SqlSession2
            openSession2 = sqlSessionFactory.openSession();
            StudentMapper mapper2=openSession2.getMapper(StudentMapper.class);
            //使用會話1進行查詢,這次查詢結果只會存儲在一級緩存中
            Student student1=mapper1.selectStudentByIdAndName(2,"hjj");
            System.out.println(student1);
            //使用會話2進行查詢,前面會話未關閉,數據不會被刷到二級緩存中,因此本次仍會執行sql
            Student student2=mapper2.selectStudentByIdAndName(2,"hjj");
            System.out.println(student2);
            //使用會話2進行查詢,因爲前面已執行過該方法,因此可在一級緩存中查到
            Student student3=mapper2.selectStudentByIdAndName(2,"hjj");
            System.out.println(student3);
            openSession1.commit();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            //最後必定不要忘記關閉 SqlSession ,不然會由於鏈接沒有關閉致使數據庫鏈接數過多,形成系統崩旗
            openSession1.close();
        }
    }
}
運行結果:
[DEBUG] - Cache Hit Ratio [MyBatisDemo.StudentMapper]: 0.0
[DEBUG] - Opening JDBC Connection
[DEBUG] - Checked out connection 234698513 from pool.
[DEBUG] - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@dfd3711]
[DEBUG] - ==>  Preparing: select * from student where sid=? and s_name=? 
[DEBUG] - ==> Parameters: 2(Integer), hjj(String)
[DEBUG] - <==      Total: 1 Student{SID=2, Sname='hjj', Sage=23, Ssex='null', course=null} [DEBUG] - Cache Hit Ratio [MyBatisDemo.StudentMapper]: 0.0
[DEBUG] - Opening JDBC Connection
[DEBUG] - Created connection 1843368112.
[DEBUG] - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@6ddf90b0]
[DEBUG] - ==>  Preparing: select * from student where sid=? and s_name=? 
[DEBUG] - ==> Parameters: 2(Integer), hjj(String)
[DEBUG] - <==      Total: 1 Student{SID=2, Sname='hjj', Sage=23, Ssex='null', course=null} [DEBUG] - Cache Hit Ratio [MyBatisDemo.StudentMapper]: 0.0 Student{SID=2, Sname='hjj', Sage=23, Ssex='null', course=null} [DEBUG] - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@dfd3711]
[DEBUG] - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@dfd3711]
[DEBUG] - Returned connection 234698513 to pool.

該顏色:表示會話1第一次查詢的結果,因爲第一次查詢,一級緩存和二級緩存中都沒有數據,因此Mapper命中率爲0.0,且進行了數據庫查詢,並將結果存儲到會話1一級緩存中。數據結構

該顏色:表示會話2第一次查詢的結果,因爲會話1沒有關閉,因此會話1的一級緩存不會刷到Mapper的二級緩存中,而且是在會話2中第一次查詢該方法,因此Mapper命中率爲0.0,且進行了數據庫查詢,並將結果存儲到會話2的一級緩存中。mybatis

該顏色:表示會話2第二次查詢的結果,雖然會話1沒有關閉,會話1的一級緩存不會刷到Mapper的二級緩存中,可是在會話2中查詢過該方法,在會話2的一級緩存中已存在該數據,因此Mapper命中率爲0.0,沒有進行數據庫查詢。
   接下來就驗證下會話中一級緩存的數據是否是隻有在該會話關閉後纔會被刷新到mapper的二級緩存!
代碼:(僅截取部分)
 

 運行結果:app

[DEBUG] - Cache Hit Ratio [MyBatisDemo.StudentMapper]: 0.0
[DEBUG] - Opening JDBC Connection
[DEBUG] - Checked out connection 234698513 from pool.
[DEBUG] - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@dfd3711]
[DEBUG] - ==>  Preparing: select * from student where sid=? and s_name=? 
[DEBUG] - ==> Parameters: 2(Integer), hjj(String)
[DEBUG] - <==      Total: 1 Student{SID=2, Sname='hjj', Sage=23, Ssex='null', course=null} [DEBUG] - Cache Hit Ratio [MyBatisDemo.StudentMapper]: 0.0 Student{SID=2, Sname='hjj', Sage=23, Ssex='null', course=null} [DEBUG] - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@dfd3711]
[DEBUG] - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@dfd3711]
[DEBUG] - Returned connection 234698513 to pool.
[DEBUG] - Cache Hit Ratio [MyBatisDemo.StudentMapper]: 0.3333333333333333 Student{SID=2, Sname='hjj', Sage=23, Ssex='null', course=null} [DEBUG] - Cache Hit Ratio [MyBatisDemo.StudentMapper]: 0.5 Student{SID=2, Sname='hjj', Sage=23, Ssex='null', course=null}
相關文章
相關標籤/搜索