首先作一個測試,建立一個mapper配置文件和mapper接口,我這裏用了最簡單的查詢來演示。java
<mapper namespace="cn.elinzhou.mybatisTest.mapper.UserMapper"> <select id="findUsers" resultType="cn.elinzhou.mybatisTest.pojo.User"> SELECT * FROM user </select> </mapper>
public interface UserMapper { List<User> findUsers()throws Exception; }
而後編寫一個單元測試spring
public class UserMapperTest { SqlSession sqlSession = null; @Before public void setUp() throws Exception { // 經過配置文件獲取數據庫鏈接信息 Reader reader = Resources.getResourceAsReader("cn/elinzhou/mybatisTest/config/mybatis.xml"); // 經過配置信息構建一個SqlSessionFactory SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader); // 經過sqlSessionFactory打開一個數據庫會話 sqlSession = sqlSessionFactory.openSession(); } @Test public void testFindUsers() throws Exception { UserMapper userMapper = sqlSession.getMapper(UserMapper.class); List<User> users = userMapper.findUsers(); System.out.println(users); } }
運行,能夠看到控制檯輸出(先配好log4j)爲相似以下圖日誌
sql
日誌說明了該操做執行的sql語句已經查詢的內容,最後一行是我手動經過System.out.printf輸出的結果。數據庫
而後再加一條語句緩存
users = userMapper.findUsers();
以前的單元測試就變成了這個樣子安全
也就是在執行完userMapper.findUsers();後馬上再執行一遍userMapper.findUsers(); 能夠想象,其實這兩個操做執行的sql是徹底相同的,並且在這期間沒有對數據庫進行過其餘操做。而後執行該單元測試,發現效果跟上面執行一條的時候徹底相同,也就是執行第二次userMapper.findUsers();操做的時候沒有對數據庫進行查詢,那麼獲得的數據是從哪裏來的?答案是一級緩存。markdown
mybatis一級緩存是指在內存中開闢一塊區域,用來保存用戶對數據庫的操做信息(sql)和數據庫返回的數據,若是下一次用戶再執行相同的請求,那麼直接從內存中讀數數據而不是從數據庫讀取。
其中數據的生命週期有兩個影響因素。session
對sqlsession執行commit操做,也就意味着用戶執行了update、delete等操做,那麼數據庫中的數據勢必會發生變化,若是用戶請求數據仍然使用以前內存中的數據,那麼將讀到髒數據。因此在執行sqlsession操做後,會清除保存數據的HashMap,用戶在發起查詢請求時就會從新讀取數據並放入一級緩存中了。mybatis
上述測試就是在第一查詢完後執行了commit操做,再進行查詢。與以前的測試不一樣的是,此次測試控制檯打印了兩組查詢結果,說明在commit以後mybatis對數據從新進行了查詢。app
通常在mybatis集成spring時,會把SqlSessionFactory設置爲單例注入到IOC容器中,不把sqlsession也設置爲單例的緣由是sqlsession是線程不安全的,因此不能爲單例。那也就意味着實際上是有關閉sqlsession的過程的。其實,對於每個service中的sqlsession是不一樣的,這是經過mybatis-spring中的org.mybatis.spring.mapper.MapperScannerConfigurer建立sqlsession自動注入到service中的。
而一級緩存的設計是每一個sqlsession單獨使用一個緩存空間,不一樣的sqlsession是不能互相訪問數據的。固然,在sqlsession關閉後,其中數據天然被清空。
特此警告!!!!
當MyBatis與spring整合後,若是沒有事務,一級緩存是失效的!一級緩存是失效的!一級緩存是失效的!
緣由就是二者結合後,sqlsession若是發現當前沒有事務,那麼每執行一個mapper方法,sqlsession就被關閉了。若是須要維持一級緩存的可用性,有兩種途徑:
在使用二級緩存以前,先測試以前提到過的關閉sqlsession後會清空緩存的問題,把junit代碼修改一下
@Test public void testFindUsers() throws Exception { UserMapper userMapper = sqlSession.getMapper(UserMapper.class); List<User> users = userMapper.findUsers(); //關閉sqlsession sqlSession.close(); //經過sqlsessionFactroy建立一個新的sqlsession sqlSession = sqlSessionFactory.openSession(); //獲取mapper對象 userMapper = sqlSession.getMapper(UserMapper.class); users = userMapper.findUsers(); System.out.println(users); }
這段代碼在第一次查詢完後關閉sqlsession,而後建立新的sqlsession和mapper來從新執行一次查詢操做,能夠預見,執行結果如圖
說明關閉了sqlsession後的確把以前的緩存數據清空了,以後再執行一樣的查詢操做也會再訪問一遍數據庫。爲了解決這個問題,須要使用二級緩存
一級緩存的做用域僅限於一個sqlsession,可是二級緩存的做用域是一個namespace。但並非意味着同一個namespace建立的mapper能夠互相讀取緩存內容,這裏的原則是,若是開啓了二級緩存,那麼在關閉sqlsession後,會把該sqlsession一級緩存中的數據添加到namespace的二級緩存中。
接下測試,先須要開啓二級緩存。
1.打開二級緩存總開關
打開總開關,只須要在mybatis總配置文件中加入一行設置
<settings> <!--開啓二級緩存--> <setting name="cacheEnabled" value="true"/> </settings>
2.打開須要使用二級緩存的mapper的開關
在須要開啓二級緩存的mapper.xml中加入caceh標籤
<cache/>
3.POJO序列化
讓須要使用二級緩存的POJO類實現Serializable接口,如
public class User implements Serializable {
經過以前三步操做就可使用二級緩存了,接下來測試。添加一個Junit方法
@Test public void testFindUsersCache() throws Exception { UserMapper userMapper = sqlSession.getMapper(UserMapper.class); List<User> users = userMapper.findUsers(); //關閉sqlsession sqlSession.close(); //經過sqlsessionFactroy建立一個新的sqlsession sqlSession = sqlSessionFactory.openSession(); //獲取mapper對象 userMapper = sqlSession.getMapper(UserMapper.class); users = userMapper.findUsers(); System.out.println(users); }
執行後能夠發現,控制檯值輸出了一次查詢過程,也能夠證實二級緩存開啓成功。
還有一個問題,以前說了,即便開啓了二級緩存,不一樣的sqlsession之間的緩存數據也不是想互訪就能互訪的,必須等到sqlsession關閉了之後,纔會把其一級緩存中的數據寫入二級緩存。爲了測試這個,把上述代碼中的
sqlSession.close();
註釋,那麼以前的代碼就變成了
再執行,發現控制太又輸出了兩次的查詢過程,因此能夠印證,只有關閉了sqlsession以後,纔會把其中一級緩存數據寫入二級緩存。
在默認狀況下,當sqlsession執行commit後會刷新緩存,可是也能夠強制設置爲不刷新,在不須要刷新的標籤中加入
flushCache="false"
如
<select id="findUsers" resultType="cn.elinzhou.mybatisTest.pojo.User" flushCache="false">
那麼,不管是否執行commit,緩存都不會刷新了。可是這樣會形成髒讀,只有在特殊狀況下才使用
有些狀況下,須要設置自動刷新緩存,那麼須要配置對應mapper中的cache標籤。
flushInterval="10000"
該屬性表示每隔10秒鐘自動刷新一遍緩存