MyBatis 和 Hibernate 同樣具備 一級緩存 和 二級緩存。java
1. 一級緩存:MyBatis 一級緩存的做用域 是 同一個SqlSession。sql
寫一個 查詢 User 的例子:數據庫
<!-- user 查詢 --> <select id="findUserById" parameterType="int" resultType="user"> select * from users where userId = #{user_id} </select>
//測試一級緩存 @Test public void testCache1() throws Exception { //獲取SqlSession SqlSession sqlSession = sqlSessionFactory.openSession(); OrderMapper orderMapper = sqlSession.getMapper(OrderMapper.class); //第一次查詢 User user = new User(); user.setUserId(100108); System.out.println("-----------------第一次查詢,從數據庫獲取--------------------"); User getUser = orderMapper.findUserById(user.getUserId()); System.out.println(getUser); System.out.println("-----------------第二次查詢,從緩存中獲取--------------------"); //再次執行一樣的查詢 User getUser2 = orderMapper.findUserById(user.getUserId()); System.out.println(getUser2); }
執行結果:
api
從結果中能夠看出:第一次查詢,在數據庫中執行了 SQL。 而第二次並無經過查詢數據庫獲得結果,緩存
而是從緩存中獲取的。那麼緩存的機制是什麼樣的?安全
SqlSession 緩存機制:執行一個SQL的時候,MyBatis 會首先去 緩存(PerpetualCache)中 根據 key 查詢session
結果集,a. 若是沒有則去查詢數據庫,查詢成功後,將結果寫入 cache 中;b. 若是有,則直接從緩存中mybatis
取出結果集返回。app
MyBatis 內部存儲 緩存使用的是一個 HashMap<CacheKey , Object>, key 爲 hashCode + sqlId 框架
+ Sql語句的格式, value 則是查詢出來映射生成的 Java 對象。
問題:若是在第二次查詢以前,更新了這個 User 的信息,那麼第二次查詢是從緩存中取出數據仍是再查詢一次?
結果是顯而易見的,在 更新、刪除操做的時,cache 會被清空。因此會再查詢一次。
2. 二級緩存:即查詢緩存,它的做用域是一個 mapper 的namespace,即在同一個 namespace 中的查詢sql
能夠從緩存中獲取數據。二級緩存是能夠跨 SqlSession 的。
a. 開啓二級緩存:在SqlMapConfig.xml 的 <settings>...</settings>中加入
<!-- 開啓二級緩存 --> <setting name="cacheEnabled" value="true"/>
屬性名 | 描述 | 容許值 | 默認值 |
cacheEnabled | 對在此配置文件下全部的 cache 進行全局性開/關設置 | true / false | true |
b. 在 mapper.xml 文件中添加 <cache /> 開啓緩存
<mapper namespace="mybatis_b.mapper.OrderMapper"> <!-- 開啓本 mapper 的二級緩存 --> <cache /> </mapper>
c. Java 調用,這裏用兩個 SqlSession 來實現二級緩存的跨 SqlSession
//測試二級緩存 @Test public void testCache2() throws Exception { //獲取SqlSession SqlSession sqlSession = sqlSessionFactory.openSession(); OrderMapper orderMapper = sqlSession.getMapper(OrderMapper.class); SqlSession sqlSession2 = sqlSessionFactory.openSession(); OrderMapper orderMapper2 = sqlSession2.getMapper(OrderMapper.class); //第一次查詢 User user = new User(); user.setUserId(100108); System.out.println("-----------------第一次查詢,從數據庫獲取--------------------"); User getUser = orderMapper.findUserById(user.getUserId()); System.out.println(getUser); //關閉session1 sqlSession.close(); System.out.println("-----------------第二次查詢,從緩存中獲取--------------------"); //再次執行一樣的查詢 User getUser2 = orderMapper2.findUserById(user.getUserId()); System.out.println(getUser2); sqlSession2.close(); }
其中,User 類要實現序列化接口。執行後發現第二次的確是從緩存中獲取數據的,結果就不貼了。
問題:若是在 第一次查詢完後,更新某個數據,那麼第二次還會從緩存中獲取數據嗎?結果是不是最新?
結果:第二次依舊是從緩存中獲取的,並且是最新的。由於:在 mapper 的同一個 namespace 中,若是有
其餘 insert、update、delete 操做數據後須要刷新緩存,若是不執行則會出現髒讀。而 mapper
的sql 中有 flushCache="true" 這個屬性,默認爲 true即刷新 mapper 級別的緩存。
3. Cache 的其餘參數:
flushInterval:刷新間隔,能夠設置爲任意的正整數,表示一個合理的時間段,以毫秒爲單位。默認狀況是
不設置,也就是沒有刷新間隔,僅在調用語句時刷新緩存。
size:引用數目,能夠設置爲任意的正整數,要記住你緩存的對象和 運行環境的可用內存資源,默認值是1024
readOnly:只讀屬性。只讀的緩存會給全部的調用者返回緩存對象相同的實例。所以這些對象不能被修改。
這提供了很重要的性能優點。可讀寫的緩存會返回緩存對象的拷貝(經過序列化)。這會慢一些
,可是安全,所以默認是false。
<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
上面這個緩存配置建立了一個FIFO緩存,並每隔60s刷新,存儲結果對象或列表的512個引用,並且返回的
對象被認爲是隻讀的,所以在不一樣的線程中調用者之間修改它們會致使衝突。
可用的回收策略有:
A. LRU - 最近最少使用的:移除最長時間不被使用的對象
B. FIFO - 先進先出:按對象進入緩存的順序來移除它們。
C. SOFT - 軟引用:移除基於垃圾回收器狀態和軟引用規則的對象。
D. WEAK - 弱引用:更積極地移除基於垃圾回收器狀態和軟引用規則的對象。
4. MyBatis 與 緩存框架 eacache 進行了整合,採用 eacache 框架管理緩存數據。
a. 引入緩存的依賴包,須要 mybatis-ehcache-1.0.3.jar ehcache-core-2.6.8.jar 和 slf4j-api-1.6.1.jar
b. 修改 mapper 的緩存配置:
<!-- 如下兩個<cache>標籤二選一,第一個能夠輸出日誌,第二個不輸出日誌 --> <cache type="org.mybatis.caches.ehcache.LoggingEhcache" /> <cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
有待詳解。。。