Mybatis的一級緩存和二級緩存詳解


緩存原理圖:

 

一、一級緩存(本地緩存)

sqlSession級別的緩存。(相當於一個方法內的緩存)

每一次會話都對應自己的一級緩存,作用范圍比較小,一旦會話關閉就查詢不到了;

一級緩存默認是一直開啟的,是SqlSession級別的一個Map;
與數據庫同一次會話期間查詢到的數據會放在本地緩存中。
以后如果需要獲取相同的數據,直接從緩存中拿,沒必要再去查詢數據庫;

測試:

 

 

取緩存中的數據:

說明:當再次查詢發現已經有數據了,就直接在緩存中返回之前查的數據,而不再訪問數據庫;

二、一級緩存失效的四種情況:

沒有使用到當前一級緩存的情況,效果就是:還需要再向數據庫發出查詢

1、sqlsession不同(會話不同)

結果:

說明:不同的會話的緩存不共享數據

2、sqlsession相同,查詢緩存中沒有的數據

@Test
public void testFirstLevelCache() throws IOException{
    SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
    SqlSession openSession = sqlSessionFactory.openSession();
    try{
        EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
        Employee emp01 = mapper.getEmpById(1);
        System.out.println(emp01);
        
        //sqlSession相同,查詢條件不同
        Employee emp03 = mapper.getEmpById(3);
        System.out.println(emp03);
        System.out.println(emp01==emp03);
 
    }finally{
        openSession.close();
    }
}

結果:

說明:當緩存中沒有數據時,會重新查數據庫

3、sqlsession相同,但兩次查詢之間執行了增刪改操作

@Test
public void testFirstLevelCache() throws IOException{
    SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
    SqlSession openSession = sqlSessionFactory.openSession();
    try{
        EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
        Employee emp01 = mapper.getEmpById(1);
        System.out.println(emp01);
        
        //sqlSession相同,兩次查詢之間執行了增刪改操作(這次增刪改可能對當前數據有影響)
        mapper.addEmp(new Employee(null, "testCache", "cache", "1"));
        System.out.println("數據添加成功");
        
        Employee emp02 = mapper.getEmpById(1);
        System.out.println(emp02);
        System.out.println(emp01==emp02);
        
    }finally{
        openSession.close();
    }
}

結果:

說明:為了防止增刪改對當前數據的影響,即使查的同一個對象,也會重新查數據庫

原因:每個增刪改標簽都有默認清空緩存配置:flushCache="true",不過這是默認的是一級和二級緩存都清空

4、sqlsession相同,但手動清楚了一級緩存(緩存清空)

清空緩存:openSession.clearCache();

@Test
public void testFirstLevelCache() throws IOException{
    SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
    SqlSession openSession = sqlSessionFactory.openSession();
    try{
        EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
        Employee emp01 = mapper.getEmpById(1);
        System.out.println(emp01);
        
        //sqlSession相同,手動清除了一級緩存(緩存清空)
        openSession.clearCache();
        
        Employee emp02 = mapper.getEmpById(1);
        System.out.println(emp02);
        System.out.println(emp01==emp02);
        
    }finally{
        openSession.close();
    }
}

結果:

說明:手動清空緩存后,需要重新查數據庫

三、二級緩存(全局緩存)

基於namespace名稱空間級別的緩存:一個namespace對應一個二級緩存

即一個mapper.xml對應一個緩存:

 

 

1、工作機制:

*         1、一個會話,查詢一條數據,這個數據就會被放在當前會話的一級緩存中;
*         2、如果會話關閉;一級緩存中的數據會被保存到二級緩存中;新的會話查詢信息,就可以參照二級緩存中的內容;
*         3、sqlSession===EmployeeMapper==>Employee
*                         DepartmentMapper===>Department
*             不同namespace查出的數據會放在自己對應的緩存中(map)
*             效果:數據會從二級緩存中獲取
*                 查出的數據都會被默認先放在一級緩存中。
*                 只有會話提交或者關閉以后,一級緩存中的數據才會轉移到二級緩存中

2、 使用:
*             1)、開啟全局二級緩存配置:<setting name="cacheEnabled" value="true"/>
*             2)、去mapper.xml中配置使用二級緩存:
*                 <cache></cache>
*             3)、我們的POJO需要實現序列化接口

1)在mybatis全局配置文件中開啟全局二級緩存配置:<setting name="cacheEnabled" value="true"/>

 

2)在mapper.xml中配置使用二級緩存

直接加上: <cache><cache/>

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
 PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.atguigu.mybatis.dao.EmployeeMapper">
    
     <cache><cache/>
 
     <!--public Map<Integer, Employee> getEmpByLastNameLikeReturnMap(String lastName);  -->
     <select id="getEmpByLastNameLikeReturnMap" resultType="com.atguigu.mybatis.bean.Employee">
         select * from tbl_employee where last_name like #{lastName}
     </select>
</mapper>

或者 <cache>中配置一些參數:

eviction:緩存的回收策略:
    • LRU – 最近最少使用的:移除最長時間不被使用的對象。
    • FIFO – 先進先出:按對象進入緩存的順序來移除它們。
    • SOFT – 軟引用:移除基於垃圾回收器狀態和軟引用規則的對象。
    • WEAK – 弱引用:更積極地移除基於垃圾收集器狀態和弱引用規則的對象。
    • 默認的是 LRU。
flushInterval:緩存刷新間隔
    緩存多長時間清空一次,默認不清空,設置一個毫秒值
readOnly:是否只讀:
    true:只讀;mybatis認為所有從緩存中獲取數據的操作都是只讀操作,不會修改數據。
             mybatis為了加快獲取速度,直接就會將數據在緩存中的引用交給用戶。不安全,速度快
    false:非只讀:mybatis覺得獲取的數據可能會被修改。
            mybatis會利用序列化&反序列的技術克隆一份新的數據給你。安全,速度慢
size:緩存存放多少元素;
type="":指定自定義緩存的全類名;
        實現Cache接口即可;

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
 PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.atguigu.mybatis.dao.EmployeeMapper">
 
     <cache eviction="FIFO" flushInterval="60000" readOnly="false" size="1024"></cache> 
 
     <!--public Map<Integer, Employee> getEmpByLastNameLikeReturnMap(String lastName);  -->
     <select id="getEmpByLastNameLikeReturnMap" resultType="com.atguigu.mybatis.bean.Employee">
         select * from tbl_employee where last_name like #{lastName}
     </select>
</mapper>

3)POJO需要實現序列化接口

測試:

注意:需要openSession.close();后,才能從二級緩存中查數據;

@Test
public void testSecondLevelCache() throws IOException{
    SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
    SqlSession openSession = sqlSessionFactory.openSession();
    SqlSession openSession2 = sqlSessionFactory.openSession();
    try{
 
        EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
        EmployeeMapper mapper2 = openSession2.getMapper(EmployeeMapper.class);
        
        Employee emp01 = mapper.getEmpById(1);
        System.out.println(emp01);
        openSession.close();
        
        //第二次查詢是從二級緩存中拿到的數據,並沒有發送新的sql
        Employee emp02 = mapper2.getEmpById(1);
        System.out.println(emp02);
        openSession2.close();
        
    }finally{
        
    }
}

結果:

說明:第二次查詢是從二級緩存中拿到的數據,並沒有發送新的sql;

注意:

如果openSession.close();在第二次查詢之后才關閉,則第二次查詢會從一級緩存中查,如果不是一個session,則查詢不到數據:

@Test
public void testSecondLevelCache() throws IOException{
    SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
    SqlSession openSession = sqlSessionFactory.openSession();
    SqlSession openSession2 = sqlSessionFactory.openSession();
    try{
 
        EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
        EmployeeMapper mapper2 = openSession2.getMapper(EmployeeMapper.class);
        
        Employee emp01 = mapper.getEmpById(1);
        System.out.println(emp01);
        
        Employee emp02 = mapper2.getEmpById(1);
        System.out.println(emp02);
        openSession.close();
        openSession2.close();
        
    }finally{
        
    }
}

結果:

說明:第二次又重新發送了sql,因為從二級緩存中取數據時,會話沒關閉所以二級緩存中沒數據,所以又去一級緩存中查詢,也沒有數據則發送了sql查數據庫;

所以,只有會話關閉或提交后,一級緩存中的數據才會轉移到二級緩存中,然后因為是同一個namespace所以可以獲取到數據;

關於Mybatis的一級緩存和二級緩存執行順序具體可參考:Mybatis的一級緩存和二級緩存執行順序

四、和緩存有關的設置/屬性

1)、mybatis全局配置文件中配置全局緩存開啟和清空

1.1)控制二級緩存的開啟和關閉

         <setting name="cacheEnabled" value="true"/>

         cacheEnabled=true:開啟緩存;false:關閉緩存(二級緩存關閉)(一級緩存一直可用的)

1.2)控制一級緩存的開啟和關閉

         <setting name="localCacheScope" value="SESSION"/>

         localCacheScope:本地緩存作用域(一級緩存SESSION);

         當前會話的所有數據保存在會話緩存中;STATEMENT:可以禁用一級緩存;

注意:一級緩存關閉后,二級緩存自然也無法使用;  

2)、方法中sqlSession清除緩存測試

sqlSession.clearCache();只是清除當前session的一級緩存;

如果openSession清空了緩存,即執行了openSession.clearCache()方法:

結果:

 說明:openSession清空緩存不影響二級緩存;只清空了一級緩存;因為在openSession.close()時,就將一級緩存保存至了二級緩存; 

3)、mapper.xml中也可以配置一級和二級緩存開啟和使用

3.1)每個select標簽都默認配置了useCache="true":
         如果useCache= false:則表示不使用緩存(一級緩存依然使用,二級緩存不使用)
3.2)每個增刪改標簽默認配置了flushCache="true":(一級二級都會清除)


增刪改執行完成后就會清除緩存;

測試:默認flushCache="true":一級緩存和二級緩存都會被清空;

執行增加操作:

結果:

 

注意:查詢標簽<select>默認flushCache="false":如果flushCache=true;每次查詢之后都會清空緩存;一級和二級緩存都無法使用;

五、整合第三方緩存

mybatis通過map實現的緩存,很不專業;此時可以通過整合第三方緩存來達到緩存的目的;

做法:實現Cache.java接口中的方法即可:

如實現putObject()方法,往緩存中寫數據;實現getObject()方法,從緩存中獲取數據;至於什么時候調用,由mybatis決定;

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM