緩存原理圖:
一、一級緩存(本地緩存)
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決定;