一級緩存和二級緩存的區別:
1、一級緩存:基於PerpetualCache的HashMap本地緩存,其存儲作用域為同一個SqlSession,當Session flush或close之后,該Session中的所有Cache就將清空。
2、二級緩存:與一級緩存其機制相同,默認也是采用PerpetualCache,HashMap存儲,不同在於其存儲作用域為Mapper(Namespace),並且可自定義存儲源,如Ehcache。
3、對於緩存數據更新機制,當某一個作用域(一級緩存Session/二級緩存Namespaces)的進行了C/U/D操作后,默認該作用域下所有select中的緩存將被clear。
如果要實現MyBatis的二級緩存,一般來說有如下兩種方式:
1. 采用MyBatis內置的Cache機制。
2. 采用三方Cache框架, 比如EhCache, OSCache等等。
下面是基於MyBatis的內置的一級和二級緩存測試:
1、一級緩存配置
默認是開啟的,如果不想用緩存,直接在select節點中增加useCache="false"和flushCache="true"屬性即可。如:
<select id="getUserArticles" parameterType="int" resultMap="resultUserArticleList" useCache="false" flushCache="true"> select user.id,user.userName,user.userAddress,article.id as aid,article.title,article.content from user,article where user.id=article.userid and user.id=#{id} </select>
測試代碼如下:
注意:此時在select節點中緩存是開啟的。
package com.jsoft.testmybatis.test1; import java.io.IOException; import java.io.InputStream; import java.util.List; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import com.jsoft.testmybatis.inter.IUserOperation; import com.jsoft.testmybatis.models.Article; import com.jsoft.testmybatis.models.User; public class App { public static void main(String[] args) throws IOException { InputStream inputStream = Resources.getResourceAsStream("Configuration.xml"); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession session = sqlSessionFactory.openSession(); try { IUserOperation userOperation = session.getMapper(IUserOperation.class); try { System.out.println("第一次查詢開始"); //List List<User> users = userOperation.selectUsers("%"); for (User tempUser : users) { System.out.println(tempUser.getUserAddress()); System.out.println(tempUser.getUserName()); } System.out.println("第一次查詢結束"); System.out.println("第二次查詢開始"); //List List<User> users2 = userOperation.selectUsers("%"); for (User tempUser : users2) { System.out.println(tempUser.getUserAddress()); System.out.println(tempUser.getUserName()); } System.out.println("第二次查詢結束"); System.out.println("加入數據開始開始"); //Add User addUser = new User(); addUser.setUserAddress("guangdong,guangzhou"); addUser.setUserName("eason"); addUser.setUserAge("80"); int addRetCount = userOperation.addUser(addUser); session.commit();//必須提交事務,否則不會寫入到數據庫。如果session不commit,那么,數據就不會放入cache中。所以,只有commit后,才能取得。 System.out.println("增加數據影響的行數:" + addRetCount); if (addUser.getId() > 0) { System.out.println("增加數據成功,新增的id為:" + addUser.getId()); } System.out.println("加入數據開始結束"); System.out.println("第三次查詢開始"); //List List<User> users3 = userOperation.selectUsers("%"); for (User tempUser : users3) { System.out.println(tempUser.getUserAddress()); System.out.println(tempUser.getUserName()); } System.out.println("第三次查詢結束"); //強制刷新緩存 session.clearCache(); System.out.println("第四次查詢開始"); //List List<User> users4 = userOperation.selectUsers("%"); for (User tempUser : users4) { System.out.println(tempUser.getUserAddress()); System.out.println(tempUser.getUserName()); } System.out.println("第三次查詢結束"); } catch (Exception e) { // TODO: handle exception session.rollback();//有異常時回滾數據 e.printStackTrace(); } } finally { session.close();//close之后緩存清空 } } }
結果如下:
可以看出,緩存生效了。
下面測試select節點中配置緩存關閉的情況,結果如下:
可以看出緩存去除了,全部都是真實查詢。
其實上面的測試示例還少了一個Session2的測試,不然效果不佳。
2、二級緩存測試:
開啟二級緩存,在XML配置文件中添加Cache節點即可,參考配置如下:
<cache eviction="FIFO" <!--回收策略為先進先出 --> flushInterval="60000" <!--自動刷新時間60s --> size="512" <!--最多緩存512個引用對象 --> readOnly="true"/> <!--只讀 -->
測試代碼如下:
package com.jsoft.testmybatis.test1; import java.io.IOException; import java.io.InputStream; import java.util.List; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import com.jsoft.testmybatis.inter.IUserOperation; import com.jsoft.testmybatis.models.Article; import com.jsoft.testmybatis.models.User; public class App2 { public static void main(String[] args) throws IOException { InputStream inputStream = Resources.getResourceAsStream("Configuration.xml"); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession session = sqlSessionFactory.openSession(); SqlSession session2 = sqlSessionFactory.openSession(); try { IUserOperation userOperation = session.getMapper(IUserOperation.class); IUserOperation userOperation2 = session.getMapper(IUserOperation.class); try { System.out.println("Session1第一次查詢開始"); //List List<User> users = userOperation.selectUsers("%"); for (User tempUser : users) { System.out.println(tempUser.getUserAddress()); System.out.println(tempUser.getUserName()); } System.out.println("Session1第一次查詢結束"); System.out.println("Session2第二次查詢開始"); //List List<User> users2 = userOperation2.selectUsers("%"); for (User tempUser : users2) { System.out.println(tempUser.getUserAddress()); System.out.println(tempUser.getUserName()); } System.out.println("Session2第二次查詢結束"); System.out.println("Session1加入數據開始開始"); //Add User addUser = new User(); addUser.setUserAddress("guangdong,guangzhou"); addUser.setUserName("eason"); addUser.setUserAge("80"); int addRetCount = userOperation.addUser(addUser); session.commit();//必須提交事務,否則不會寫入到數據庫。如果session不commit,那么,數據就不會放入cache中。所以,只有commit后,才能取得。 System.out.println("增加數據影響的行數:" + addRetCount); if (addUser.getId() > 0) { System.out.println("增加數據成功,新增的id為:" + addUser.getId()); } System.out.println("Session1加入數據開始結束"); System.out.println("Session1第三次查詢開始"); //List List<User> users3 = userOperation.selectUsers("%"); for (User tempUser : users3) { System.out.println(tempUser.getUserAddress()); System.out.println(tempUser.getUserName()); } System.out.println("Session1第三次查詢結束"); //強制刷新緩存 session.clearCache(); System.out.println("Session1第四次查詢開始"); //List List<User> users4 = userOperation.selectUsers("%"); for (User tempUser : users4) { System.out.println(tempUser.getUserAddress()); System.out.println(tempUser.getUserName()); } System.out.println("Session1第四次查詢結束"); System.out.println("Session2第五次查詢開始"); //List List<User> users5 = userOperation2.selectUsers("%"); for (User tempUser : users5) { System.out.println(tempUser.getUserAddress()); System.out.println(tempUser.getUserName()); } System.out.println("Session2第五次查詢結束"); } catch (Exception e) { // TODO: handle exception session.rollback();//有異常時回滾數據 session2.rollback();//有異常時回滾數據 e.printStackTrace(); } } finally { session.close();//close之后緩存清空 session2.close();//close之后緩存清空 } } }
測試結果如下:
可以看出Cache不受Session的限制,且操作緩存的方法是一致的。
3、總結
1、映射語句文件中的所有select語句將會被緩存。
2、映射語句文件中的所有insert,update和delete語句會刷新緩存。
3、緩存會使用Least Recently Used(LRU,最近最少使用的)算法來收回。
4、緩存會根據指定的時間間隔來刷新。
5、每個緩存可以存儲 1024 個列表或對象的引用(不管查詢出來的結果是什么) 。
6、緩存將作為“讀/寫”緩存,意味着獲取的對象不是共享的且對調用者是安全的。不會有其它的調用者或線程潛在修改。
4、參考:
http://www.yihaomen.com/article/java/428.htm(文中結論主要轉自此篇)
http://www.cnblogs.com/xdp-gacl/p/4270403.html(文中結論主要轉自此篇)
http://blog.csdn.net/u012373815/article/details/47069223
http://blog.csdn.net/luanlouis/article/details/41390801
http://blog.csdn.net/luanlouis/article/details/41280959
http://blog.csdn.net/luanlouis/article/details/41408341
5、測試工程:https://github.com/easonjim/5_java_example/tree/master/mybatis/test12
下面是第三方緩存框架EhCache的配置使用:
說明:為什么要使用EhCache,因為它提供了很多內置緩存沒有的強大功能,比如緩存的集群等。但是有一點,EhCache使用的日志框架是slf4j,需要而外配置使用slf4j,如果不想使用slf4j,也可以配置使用log4j。下面就是使用log4j的示例。
1、添加POM依賴:
<!-- ehcache --> <!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache --> <dependency> <groupId>org.mybatis.caches</groupId> <artifactId>mybatis-ehcache</artifactId> <version>1.1.0</version> </dependency> <!-- change slf4j for log4j --> <!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-log4j12 --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.25</version> </dependency>
而對於使用log4j組件時,需要下載一個特殊的包slf4j-log4j12才能正常輸出日志,參考:http://jiajun-kucoo.blog.163.com/blog/static/64148688201352791439772/
2、在resources中增加ehcache.xml文件,內容如下:
<?xml version="1.0" encoding="UTF-8"?> <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../config/ehcache.xsd"> <diskStore path="D:/ehcache" /> <!-- name:Cache的唯一標識 maxElementsInMemory:內存中最大緩存對象數 maxElementsOnDisk:磁盤中最大緩存對象數,若是0表示無窮大 eternal:Element是否永久有效,一但設置了,timeout將不起作用 overflowToDisk:配置此屬性,當內存中Element數量達到maxElementsInMemory時,Ehcache將會Element寫到磁盤中 timeToIdleSeconds:設置Element在失效前的允許閑置時間。僅當element不是永久有效時使用,可選屬性,默認值是0,也就是可閑置時間無窮大 timeToLiveSeconds:設置Element在失效前允許存活時間。最大時間介於創建時間和失效時間之間。僅當element不是永久有效時使用,默認是0.,也就是element存活時間無窮大 diskPersistent:是否緩存虛擬機重啟期數據 diskExpiryThreadIntervalSeconds:磁盤失效線程運行時間間隔,默認是120秒 diskSpoolBufferSizeMB:這個參數設置DiskStore(磁盤緩存)的緩存區大小。默認是30MB。每個Cache都應該有自己的一個緩沖區 memoryStoreEvictionPolicy:當達到maxElementsInMemory限制時,Ehcache將會根據指定的策略去清理內存。默認策略是LRU(最近最少使用)。你可以設置為FIFO(先進先出)或是LFU(較少使用) 備注: 持久化到硬盤的路徑由虛擬機參數"java.io.tmpdir"決定. 例如, 在Windows中, 會在此路徑下 C:\Documents and Settings\Jim\Local Settings\Temp,在Linux中, 通常會在:/tmp下,測試:System.out.println(System.getProperty("java.io.tmpdir")); --> <defaultCache maxElementsInMemory="1000" maxElementsOnDisk="10000000" eternal="false" overflowToDisk="true" timeToIdleSeconds="120" timeToLiveSeconds="120" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="FIFO"> </defaultCache> <!-- <cache name="test" overflowToDisk="true" eternal="false" timeToIdleSeconds="300" timeToLiveSeconds="600" maxElementsInMemory="1000" maxElementsOnDisk="10" diskPersistent="true" diskExpiryThreadIntervalSeconds="300" diskSpoolBufferSizeMB="100" memoryStoreEvictionPolicy="LRU" /> --> </ehcache>
詳細配置參考:http://blog.csdn.net/etttttss/article/details/17141485
3、在user.xml中,也就是Mapper文件配置第三方緩存:
<!-- 以下兩個<cache>標簽二選一,第一個可以輸出日志,第二個不輸出日志 --> <cache type="org.mybatis.caches.ehcache.LoggingEhcache" /> <!-- <cache type="org.mybatis.caches.ehcache.EhcacheCache"/> -->
4、最后在log4j.properties文件中增加日志的切入點
log4j.logger.net.sf.ehcache=DEBUG
這句話的意思是在這個包net.sf.ehcache下的DEBUG級別的日志全部輸出。
5、打印的日志如下:
6、參考:
http://www.yihaomen.com/article/java/428.htm
http://www.mybatis.org/ehcache-cache/dependencies.html
7、測試工程:https://github.com/easonjim/5_java_example/tree/master/mybatis/test13
自定義自己的緩存:
其實從上面的配置來看,<cache type="org.mybatis.caches.ehcache.LoggingEhcache" />節點中的type指定的是具體的類,而這個類繼承的是Cache接口,所以只要自己自定義繼承這個接口實現自己特定類也能達到緩存效果。
基本上不用自己寫這些自定義緩存,因為提供的第三方緩存框架來說已經很強大了。