Mybatis延遲載入、快取
一、Mybatis中的延遲載入
1、延遲載入背景:Mybatis中Mapper配置檔案中的resultMap可以實現高階對映(使用association、collection實現一對一及一對多(多對多)對映),同樣的association、collection具備延遲載入功能。所謂延遲載入,就是先單表查詢,需要時再從關聯表去關聯查詢(同樣也可能只是是單表查詢),大大單表查詢速度更快,所以可以間接的提高資料庫效能
2、在mybatis核心配置檔案中配置,其中lazyLoadingEnabled表示懶載入開關、aggressiveLazyLoading表示非懶載入(積極載入),通過在Mybatis核心配置檔案中配置這些屬性的值來使用Mybatis的懶載入,具體配置方式如下:
<settings> <!--懶載入模式在Mybatis中預設是關閉的--> <setting name="lazyLoadingEnabled" value="true"/> <!--不同於懶載入的:積極載入方式,所以在懶載入的時候設定該屬性為false--> <setting name="aggressiveLazyLoading" value="false"></setting> </settings>
3、由於是使用懶載入,所以我們顯然可以將Mapper配置檔案中的查詢分為兩張單表查詢的statment,其中User表的查詢放在Order查詢配置的resultMap中,並進行延遲載入的設定
<select id="findUserByUid" parameterType="int" resultType="cn.mybatis.po.User"> SELECT * FROM USER WHERE uid = #{id} </select> <resultMap id="OrderAndUserByLazyLoading" type="cn.mybatis.po.Order"> <id column="oid" property="oid"></id> <result column="total" property="total"></result> <result column="ordertime" property="ordertime"></result> <result column="name" property="name"></result> <!-- 實現延遲載入功能 select:指定延遲載入需要執行的statment的id(即根據使用者id查詢使用者資訊的select的statment) column:關聯查詢的列資訊--> <association property="user" javaType="cn.mybatis.po.User" select="findUserByUid" column="uid"> </association> </resultMap> <select id="findOrderAndUserByLazyLoading" resultMap="OrderAndUserByLazyLoading"> SELECT * FROM orders </select> LazyLoading配置檔案資訊
4、在Mapper.java中添加了延遲載入的測試方法
//延遲載入測試方法 public List<Order> findOrderAndUserByLazyLoading() throws Exception;
5、使用Junit測試延遲載入的測試程式碼
1@Test 2public void testFindOrderAndUserByLazyLoading() throws Exception { 3SqlSession sqlSession = sqlSessionFactory.openSession(); 4OrderMapper orderMapper = sqlSession.getMapper(OrderMapper.class); 5 6List<Order> orderList= orderMapper.findOrderAndUserByLazyLoading(); 7 8for (Order order : orderList) { 9System.out.println(order.getUser()); 10} 11 12sqlSession.close(); 13}
6、測試結果,從測試結果可以看出,我們首先只是單表查詢了order是表的資訊,然後在遍歷查詢到的結果(列印User資訊)的時候,又發出查詢user資訊的Sql,從而實現了延遲載入的功能
二、Mybatis中的一級快取
1、一級快取是在SqlSession 層面進行快取的。即在同一個SqlSession 中,多次呼叫同一個Mapper中的同一個statment並且是同一個引數的話,只會進行一次資料庫查詢,然後把資料快取到緩衝中,如果以後要查詢相同的Sql和引數,就直接先從快取中取出資料,不會直接去查資料庫。 但是不同的SqlSession物件,因為不用的SqlSession都是相互隔離的,所以相同的Mapper、引數和方法,他還是會再次傳送到SQL到資料庫去執行,返回結果。(本質上是在SqlSession作用域下面的HashMap本地快取,當 SqlSession 重新整理或 關閉之後,該Session中的所有 快取資料就將清空。)可以用下面的這張圖來表示一級快取
2、我們來使用一級快取進行測試,首先通過上面一級快取的簡單定義,我們可以得到下面的這張簡略圖,用以示解一級快取。在例項圖中,第一次查詢某條記錄時候,Mybatis所做的就是將查詢到的結果放在該SqlSession的快取中,如果期間沒有該資料的修改、刪除、或者增加操作,那麼之後再讀取該資料就會直接從快取中得到資料,而不用再向資料庫發Sql請求,當然,如果第一次查詢之後,對資料進行了delete、update、insert操作,那麼就會刪除快取中的資料,這樣做的目的也很顯然,保證資料的最新性,避免出現髒讀的情況。
3、一級快取的測試(Mybatis中預設開啟的是一級快取)
做個簡單的測試:按照上面的圖中所示,我們查詢兩次id=1的User資訊,並且兩次查詢期間沒有進行會清空快取的操作,結果應該是隻向資料庫傳送一次Sql查詢
1@Test 2public void testUpdateUserInfo() throws Exception { 3SqlSession sqlSession = sqlSessionFactory.openSession(); 4UserMapper userMapper = sqlSession.getMapper(UserMapper.class); 5 6User user1 = userMapper.findUserById(1); 7System.out.println(user1); 8 9User user2 = userMapper.findUserById(1); 10System.out.println(user2); 11 12sqlSession.close(); 13}
4、我們通過觀察日誌可以看出,只是在第一次查詢的時候傳送了Sql,第二次是直接列印user資訊
當然,接下來要做的測試就是在兩次查詢期間做insert操作,然後觀察日誌,結果應該是發現會想資料庫傳送兩次sql
1@Test 2public void testUpdateUserInfo() throws Exception { 3SqlSession sqlSession = sqlSessionFactory.openSession(); 4UserMapper userMapper = sqlSession.getMapper(UserMapper.class); 5 6User user1 = userMapper.findUserById(1); 7System.out.println(user1); 8 9User user = new User("InsertTest","insert","insert","man"); 10userMapper.insertUserInfo(user); 11sqlSession.commit(); 12 13User user2 = userMapper.findUserById(1); 14System.out.println(user2); 15 16sqlSession.close(); 17}
5、我們在測試程式碼中加了insert之後,通過觀察日誌可以發現,在查詢過程中,向Database傳送了兩條select語句,可以驗證上面的猜想
三、Mybatis中的二級快取
1、二級快取的實現機制基本上和一級快取機制相同,不同的作用域不一樣,二級快取區域在一個個的mapper中。顯然,由於多個SqlSession可以操作同一個mapper,所以二級快取比一級快取域更大。二級快取按照mapper劃分,簡而言之,也可說成按照mapper中的namespace進行劃分,這樣看來,每一個namespace下面都有一個二級快取區域,而如果兩個mapper的namespace相同,那麼資料會快取在相同的快取區域中。當然,類似於一級快取的特點,如果不同的SqlSession進行資料的insert、delete、update操作的話,也會清空二級快取中的資料
2、開啟二級快取後,進行測試。具體使用二級快取在配置檔案中的配置為:
首先在Mybatis的核心配置檔案中配置二級快取(本專案中的SQLMapConfig.xml)
<!--settings配置二級快取 --> <settings> <setting name="cacheEnabled" value="true"></setting> </settings>
然後在需要配置二級快取的特定mapper配置檔案中進行新增二級快取的配置
3、編寫測試程式並執行
1@Test 2public void testCache() throws Exception { 3SqlSession sqlSession1 = sqlSessionFactory.openSession(); 4UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class); 5User user1 = userMapper1.findUserById(1); 6System.out.println(user1); 7//需要將SqlSession關閉才能將資料寫入快取 8sqlSession1.close(); 9 10 11SqlSession sqlSession2 = sqlSessionFactory.openSession(); 12UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class); 13User user2 = userMapper2.findUserById(1); 14System.out.println(user2); 15sqlSession2.close(); 16 17 18}
在執行的時候出現了下面的異常,原因就是沒有實現序列化介面,由於快取資料可能再本地記憶體中,也可能在其他儲存介質上,所以存在物件的序列化和反序列化
所以在實現序列化介面之後,再次執行,得到下面的結果
四、Mybatis和ehcache整合
1、首先說明ehcache是一個分散式的快取框架,而使用Mybatis和ehcache進行整合的時候,首先就需要匯入ehcache的jar包和mybatis與ehcache整合的jar包,如下圖所示
2、下面是mybatis-ehcache整合jar包中的Cache介面實現類
3、然後我們在Mapper配置檔案中配置二級快取
<!--關於cache標籤的一些屬性說明: type:指定Mybatis中預設實現的cache介面的實現類型別,Mybatis中預設使用PerpetualCache 如果和ehcache整合,需要將type配置為ehcache實現cache的實現類型別 --> <cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
4、下面是mybatis和ehcache整合之後的測試結果