Mybatis提供對緩存的支持,分為一級緩存和二級緩存,在沒有配置的情況下,系統默認會使用一級緩存。
一級緩存(SqlSession級別)
我們都知道每個SqlSession對象之間的緩存是互不影響的,當同一個SqlSession執行多次相同的SQL語句時(主要針對select),系統只會到底層訪問數據庫一次,后續執行sql時,會從一級緩存里面讀取第一次訪問的數據。當這個SqlSession對象執行close(),或者顯示聲明清空緩存時,對應的一級緩存也會清空,從而提高效率。值得注意的是,如果SqlSession執行DML操作(即insert、update、delete),并提交到數據庫時,也會起到清空該SqlSession對象對應的一級緩存的作用,目的是為了保證緩存里面的數據是最新的。,避免臟讀現象。
二級緩存(SqlSessionFactory級別)
有些書籍說二級緩存是Mapper級別,可能是依據二級緩存的配置是在xxxMapper.xml上配置的吧,不過本人更認同是SqlSessionFactory級別,為什么呢?因為同一個Configuration里面是創建一個SqlSessionFactory,SqlSessionFactory是屬于線程安全的,SqlSessionFactory可以創建很多個SqlSession來進行事務操作,也就是說,SqlSessionFactory是由很多個SqlSession對象共享的,同樣的,二級緩存的數據也是由很多個SqlSession共享的。如何才能讓系統操作二級緩存呢?原理很簡單,不同的SqlSession對象執行相同的namespace下的sql語句,當第一個SqlSession對象調用close()關閉一級緩存時,第一次查詢得到的數據將會被保存到二級緩存,后面的SqlSession對象調用相同sql語句時,就會從二級緩存中獲取數據。當然,使用二級緩存,需要對應的返回對象(即POJO)實現序列化。
配置:在需要使用的xxxMapper.xml里面配置<cache eviction="LRU" flushInterval="100000" size="1024" readOnly="true" />
eviction:代表回收策略,目前支持4種策略
1.LRU,最近最少使用的,移除最長時間不用的對象;
2.FIFO,先進先出;
3.SOFT,移除基于垃圾回收器狀態和軟引用規則的對象;
4.WEAK,更積極移除基于垃圾回收器狀態和弱引用規則的對象。
flushInterval:代表刷新時長,單位毫秒
size:代表緩存最多可以存儲多少個對象,注意,設置過大會導致內存溢出
readOnly:意味著緩存數據只能讀取,不能修改
上面是較為詳細配置,當然也可以簡單一點,直接寫上<cache />就可以了。
下面給出一個較為有意思的栗子:
xml配置代碼如下:
<?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.learn.mapper.EmployeeMapper"><cache /><select id="getEmpList" resultType="employee">select * from tb_employee</select></mapper>
JUnit4測試代碼如下:
@Testpublic void testCache(){SqlSession ss1 = null;SqlSession ss2 = null;try{ss1 = SqlSessionFactoryUtil.initSqlSessionFactory().openSession();ss2 = SqlSessionFactoryUtil.initSqlSessionFactory().openSession();EmployeeMapper em1 = ss1.getMapper(EmployeeMapper.class);EmployeeMapper em2 = ss2.getMapper(EmployeeMapper.class);List<Employee> list1 = em1.getEmpList();//ss1.close();list1 = em2.getEmpList();}catch(Exception e){ss1.rollback();ss2.rollback();e.printStackTrace();}finally{if(ss1 != null){ss1.close();}if(ss2 != null){ss2.close();}}}
代碼中實例化兩個SqlSession對象,分別是ss1和ss2,用戶檢驗數據讀取的操作。留意上面注釋了ss1.close();這一行。
下面是執行上面測試代碼的日志結果:
Logging initialized using 'class org.apache.ibatis.logging.slf4j.Slf4jImpl' adapter. PooledDataSource forcefully closed/removed all connections. PooledDataSource forcefully closed/removed all connections. PooledDataSource forcefully closed/removed all connections. PooledDataSource forcefully closed/removed all connections. Cache Hit Ratio [com.learn.mapper.EmployeeMapper]: 0.0 Opening JDBC Connection Created connection 275310919. Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@1068e947] ==> Preparing: select * from tb_employee ==> Parameters: <== Total: 6 Cache Hit Ratio [com.learn.mapper.EmployeeMapper]: 0.0 Opening JDBC Connection Created connection 1948863195. Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@74294adb] ==> Preparing: select * from tb_employee ==> Parameters: <== Total: 6 Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@1068e947] Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@1068e947] Returned connection 275310919 to pool. Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@74294adb] Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@74294adb] Returned connection 1948863195 to pool.
可以看到即使配置了<cache />二級緩存,第一個SqlSession對象ss1沒有手動執行close()方法時,ss1對應的一級緩存數據仍然沒有保存到二級緩存里面,即二級緩存里面沒有數據,當第二個SqlSession對象ss2執行相同的sql語句時,會找一級緩存有沒有數據,因為第一次執行,當然沒有數據了,然后找二級緩存,剛才說過了,ss1的一級緩存數據并沒有保存到二級緩存里面,所以二級緩存也沒有數據,因此ss2就會再次操作數據庫。可以看到log日志會有兩條select語句。
此時,如果將close();的注釋去掉,再執行一下測試代碼,日志結果如下:
Logging initialized using 'class org.apache.ibatis.logging.slf4j.Slf4jImpl' adapter. PooledDataSource forcefully closed/removed all connections. PooledDataSource forcefully closed/removed all connections. PooledDataSource forcefully closed/removed all connections. PooledDataSource forcefully closed/removed all connections. Cache Hit Ratio [com.learn.mapper.EmployeeMapper]: 0.0 Opening JDBC Connection Created connection 275310919. Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@1068e947] ==> Preparing: select * from tb_employee ==> Parameters: <== Total: 6 Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@1068e947] Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@1068e947] Returned connection 275310919 to pool. Cache Hit Ratio [com.learn.mapper.EmployeeMapper]: 0.5
顯而易見,此時只訪問一次數據庫。