一,MyBatis一級緩存(本地緩存)
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,且兩次查詢過程中沒有更新信息,不會導致一級緩存失效,所以結果只進行了一次數據庫查詢。
那如果是在兩個SqlSession中分別進行查詢呢?
代碼:

結果:
[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的一級緩存中。
該顏色:表示會話2第二次查詢的結果,雖然會話1沒有關閉,會話1的一級緩存不會刷到Mapper的二級緩存中,但是在會話2中查詢過該方法,在會話2的一級緩存中已存在該數據,所以Mapper命中率為0.0,沒有進行數據庫查詢。
接下來就驗證下會話中一級緩存的數據是不是只有在該會話關閉后才會被刷新到mapper的二級緩存!
代碼:(僅截取部分)

運行結果:
[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}