本篇主要介紹下mybatis中的延遲加載和一二級緩存
Mybatis延遲加載
1,什麼是延遲加載?
resultMap可以實現高級映射(使用association、collection實現一對一及一對多映射),association、collection具備延遲加載功能。
需求:如果查詢訂單並且關聯查詢用戶信息。如果先查詢訂單信息即可滿足要求,當我們需要查詢用戶信息時再查詢用戶信息。把對用戶信息的按需去查詢就是延遲加載
延遲加載:先從單表查詢、需要時再從關聯表去關聯查詢,大大提高 數據庫性能,因爲查詢單表要比關聯查詢多張錶速度要快。
2,使用association實現延遲加載(collection類似)
需求:查詢訂單並關聯查詢用戶信息
在上篇文章(mybatis學習三)中對於這個需要我們當時是採用resultMap一對一關聯查詢方式實現的,當時是直接去查詢訂單表和用戶表將數據查詢出來,這次我們採用延遲加載的方式去實現這個需求。
sql的寫法上發生了變化:
之前非延遲加載的sql是這樣的:
SELECT
orders.*,
USER.username,
USER.sex
FROM
orders,
USER
WHERE
orders.user_id= user.id
現在需要分別按需去查詢兩張表,則sql可以理解爲:
關聯查詢的sql理解爲:
SELECT orders.*,
(SELECT username FROM USER WHERE orders.user_id = user.id)username,
(SELECT sex FROM USER WHERE orders.user_id = user.id)sex
FROM orders
mapper.xml
需要定義兩個mapper的方法對應的statement
1,只查詢訂單信息
SELECT * FROM orders
在查詢訂單的statement中使用association去延遲加載(執行)下邊的satatement(關聯查詢用戶信息)
2,關聯查詢用戶信息
通過上邊查詢到的訂單信息中user_id去關聯查詢用戶信息
上邊先去執行findOrdersUserLazyLoading,當需要去查詢用戶的時候再去執行findUserById,通過resultMap的定義將延遲加載執行配置起來。
3,核心:延遲加載的resultMap定義
使用association中的select指定延遲加載去執行的statement的id(collection與之類似)
<!-- 延遲加載的resultMap -->
<resultMap type="cn.travelsky.mybatis.po.Orders" id="OrdersUserLazyLoadingResultMap">
<!--對訂單信息進行映射配置 -->
<id column="id" property="id"/>
<result column="user_id" property="userId"/>
<result column="number" property="number"/>
<result column="createtime" property="createtime"/>
<result column="note" property="note"/>
<!-- 實現對用戶信息進行延遲加載
select:指定延遲加載需要執行的statement的id(是根據user_id查詢用戶信息的statement)
要使用userMapper.xml中findUserById完成根據用戶id(user_id)用戶信息的查詢,如果findUserById不在本mapper中需要前邊加namespace
column:訂單信息中關聯用戶信息查詢的列,是user_id
-->
<association property="user" javaType="cn.travelsky.mybatis.po.User"
select="cn.travelsky.mybatis.mapper.UserMapper.findUserById" column="user_id">
<!-- 實現對用戶信息進行延遲加載 -->
</association>
</resultMap>
mapper.java
延遲加載參數配置:
mybatis默認沒有開啓延遲加載,需要在SqlMapConfig.xml中setting配置:lazyLoadingEnabled、aggressiveLazyLoading
lazyLoadingEnabled(全局性設置懶加載。如果設爲‘false’,則所有相關聯的都會被初始化加載。)
aggressiveLazyLoading(當設置爲‘true’的時候,懶加載的對象可能被任何懶屬性全部加載。否則,每個屬性都按需加載。)
在SqlMapConfig.xml中配置:
測試思路:
1、執行上邊mapper方法(findOrdersUserLazyLoading),內部去調用cn.travelsky.mybatis.mapper.OrdersMapperCustom中的findOrdersUserLazyLoading只查詢orders信息(單表)。
2、在程序中去遍歷上一步驟查詢出的List<Orders>,當我們調用Orders中的getUser方法時,開始進行延遲加載。
3、延遲加載,去調用UserMapper.xml中findUserbyId這個方法獲取用戶信息。
測試代碼:
測試結果分析:
如上圖所示結果:
1,首先查詢訂單表發出了sql(select * from Orders )
2,當遍歷訂單需要查詢用戶信息時才按需查詢用戶表發出sql(select * from user where id=?)(Parameters: 1(Integer)
3,此時查詢出了三號訂單的數據(Orders [id=3, userId=1, number=1000010, createtime=Wed Feb 04 13:22:35 CST 2015, note=這是三號訂單, user=User [id=1, username=王五, sex=2, birthday=null, address=null], orderdetails=null])
4,此時接着在遍歷第二條數據,本來應該發送sql的,但是由於用戶user_id值與第一條的相同,此時沒有發送sql從數據庫中查詢數據而是讀取的緩存中數據。因此沒有發送sql語句。(Orders [id=4, userId=1, number=1000011, createtime=Tue Feb 03 13:22:41 CST 2015, note=這是四號訂單, user=User [id=1, username=王五, sex=2, birthday=null, address=null], orderdetails=null])
5,然後繼續遍歷到第三條的時候由於user_id變成了10,這次沒有緩存數據,因此發出了新的sql(Orders [id=5, userId=10, number=1000012, createtime=Thu Feb 12 16:13:23 CST 2015, note=這是五號訂單, user=User [id=10, username=張三, sex=1, birthday=Thu Jul 10 00:00:00 CST 2014, address=北京市], orderdetails=null])
總結:
不使用mybatis提供的association及collection中的延遲加載功能,如何實現延遲加載??
實現方法如下:
定義兩個mapper方法:
1、查詢訂單列表
2、根據用戶id查詢用戶信息
實現思路:
先去查詢第一個mapper方法,獲取訂單信息列表
在程序中(service),按需去調用第二個mapper方法去查詢用戶信息。
總之:
使用延遲加載方法,先去查詢簡單的sql(最好單表,也可以關聯查詢),再去按需要加載關聯查詢的其它信息。
Mybatis查詢緩存
1,什麼是查詢緩存
mybatis提供查詢緩存,用於減輕數據壓力,提高數據庫性能。
mybaits提供一級緩存,和二級緩存。
一級緩存是SqlSession級別的緩存。在操作數據庫時需要構造 sqlSession對象,在對象中有一個數據結構(HashMap)用於存儲緩存數據。不同的sqlSession之間的緩存數據區域(HashMap)是互相不影響的。
二級緩存是mapper級別的緩存,多個SqlSession去操作同一個Mapper的sql語句,多個SqlSession可以共用二級緩存,二級緩存是跨SqlSession的。
爲什麼要用緩存?
如果緩存中有數據就不用從數據庫中獲取,大大提高系統性能。
一級緩存工作原理
第一次發起查詢用戶id爲1的用戶信息,先去找緩存中是否有id爲1的用戶信息,如果沒有,從數據庫查詢用戶信息。
得到用戶信息,將用戶信息存儲到一級緩存中。
如果sqlSession去執行commit操作(執行插入、更新、刪除),清空SqlSession中的一級緩存,這樣做的目的爲了讓緩存中存儲的是最新的信息,避免髒讀。
第二次發起查詢用戶id爲1的用戶信息,先去找緩存中是否有id爲1的用戶信息,緩存中有,直接從緩存中獲取用戶信息。
一級緩存測試驗證
mybatis默認支持一級緩存,不需要在配置文件去配置。
按照上邊一級緩存原理步驟去測試。
@Test
public void testCache1() throws Exception{
SqlSession sqlSession = sqlSessionFactory.openSession();//創建代理對象
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
//下邊查詢使用一個SqlSession
//第一次發起請求,查詢id爲1的用戶
User user1 = userMapper.findUserById(1);
System.out.println(user1);
// 如果sqlSession去執行commit操作(執行插入、更新、刪除),清空SqlSession中的一級緩存,這樣做的目的爲了讓緩存中存儲的是最新的信息,避免髒讀。
//更新user1的信息
user1.setUsername("測試用戶");
userMapper.updateUser(user1);
//執行commit操作去清空緩存
sqlSession.commit();
//第二次發起請求,查詢id爲1的用戶
User user2 = userMapper.findUserById(1);
System.out.println(user2);
sqlSession.close();
}
1,首先不執行commit操作,兩次執行userMapper.findUserById(1)去查詢用戶信息,通過控制檯打印信息可以判斷,第一次執行查詢時發出了sql,第二次沒有發生sql。得出結論,第二次從緩存中獲取的數據。
如下圖所示:
2,在兩次查詢操作之間插入commit操作,此時清空了sqlSession中的一級緩存,因此第二次查詢用戶信息時重新發出了sql去數據庫中查詢。
一級緩存的應用:
是將mybatis和spring進行整合開發,事務控制在service中。
一個service方法中包括 很多mapper方法調用。
service{
//開始執行時,開啓事務,創建SqlSession對象
//第一次調用mapper的方法findUserById(1)
//第二次調用mapper的方法findUserById(1),從一級緩存中取數據
//方法結束,sqlSession關閉
}
如果是執行兩次service調用查詢相同 的用戶信息,不走一級緩存,因爲session方法結束,sqlSession就關閉,一級緩存就清空。
二級緩存
原理:
首先開啓mybatis的二級緩存。
sqlSession1去查詢用戶id爲1的用戶信息,查詢到用戶信息會將查詢數據存儲到二級緩存中。
如果SqlSession3去執行相同 mapper下sql,執行commit提交,清空該 mapper下的二級緩存區域的數據。
sqlSession2去查詢用戶id爲1的用戶信息,去緩存中找是否存在數據,如果存在直接從緩存中取出數據。
二級緩存與一級緩存區別,二級緩存的範圍更大,多個sqlSession可以共享一個UserMapper的二級緩存區域。
UserMapper有一個二級緩存區域(按namespace分) ,其它mapper也有自己的二級緩存區域(按namespace分)。
每一個namespace的mapper都有一個二緩存區域,兩個mapper的namespace如果相同,這兩個mapper執行sql查詢到數據將存在相同 的二級緩存區域中。
mybaits的二級緩存是mapper範圍級別,除了在SqlMapConfig.xml設置二級緩存的總開關,還要在具體的mapper.xml中開啓二級緩存。
1,在覈心配置文件SqlMapConfig.xml中加入
<setting name="cacheEnabled" value="true"/>
2,在UserMapper.xml中開啓二緩存,UserMapper.xml下的sql執行完成會存儲到它的緩存區域(HashMap)。
調用pojo類實現反序列化接口
爲了將緩存數據取出執行反序列化操作,因爲二級緩存數據存儲介質多種多樣,不一樣在內存。
測試代碼:
SqlSession sqlSession1 = sqlSessionFactory.openSession();
UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
User user1 = userMapper1.findUserById(1);
System.out.println(user1);
sqlSession1.close();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
User user2 = userMapper2.findUserById(1);
System.out.println(user2);
sqlSession2.close();
測試結果:
與一級緩存類似,如果在兩次查詢之間有commit操作,則二級緩存會被刷新,避免髒讀。
// 二級緩存測試
@Test
public void testCache2() throws Exception {
SqlSession sqlSession1 = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
SqlSession sqlSession3 = sqlSessionFactory.openSession();
// 創建代理對象
UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
// 第一次發起請求,查詢id爲1的用戶
User user1 = userMapper1.findUserById(1);
System.out.println(user1);
//這裏執行關閉操作,將sqlsession中的數據寫到二級緩存區域
sqlSession1.close();
//使用sqlSession3執行commit()操作
UserMapper userMapper3 = sqlSession3.getMapper(UserMapper.class);
User user = userMapper3.findUserById(1);
user.setUsername("張明明");
userMapper3.updateUser(user);
//執行提交,清空UserMapper下邊的二級緩存
sqlSession3.commit();
sqlSession3.close();
UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
// 第二次發起請求,查詢id爲1的用戶
User user2 = userMapper2.findUserById(1);
System.out.println(user2);
sqlSession2.close();
}
useCache配置
在statement中設置useCache=false可以禁用當前select語句的二級緩存,即每次查詢都會發出sql去查詢,默認情況是true,即該sql使用二級緩存。
<select id="findOrderListResultMap" resultMap="ordersUserMap" useCache="false">
總結:針對每次查詢都需要最新的數據sql,要設置成useCache=false,禁用二級緩存。
刷新緩存(就是清空緩存)
在mapper的同一個namespace中,如果有其它insert、update、delete操作數據後需要刷新緩存,如果不執行刷新緩存會出現髒讀。
設置statement配置中的flushCache="true" 屬性,默認情況下爲true即刷新緩存,如果改成false則不會刷新。使用緩存時如果手動修改數據庫表中的查詢數據會出現髒讀。
如下:
<insert id="insertUser" parameterType="cn.travelsky.mybatis.po.User" flushCache="true">
總結:一般下執行完commit操作都需要刷新緩存,flushCache=true表示刷新緩存,這樣可以避免數據庫髒讀。
分佈式緩存
我們系統爲了提高系統併發,性能、一般對系統進行分佈式部署(集羣部署方式)
不使用分佈緩存,緩存的數據在各各服務單獨存儲,不方便系統 開發。所以要使用分佈式緩存對緩存數據進行集中管理。
mybatis無法實現分佈式緩存,需要和其它分佈式緩存框架進行整合。
mybatis整合ehcache
ehcache是一個分佈式緩存框架。
整合方法:
mybatis提供了一個cache接口,如果要實現自己的緩存邏輯,實現cache接口開發即可。
mybatis和ehcache整合,mybatis和ehcache整合包中提供了一個cache接口的實現類。現在只需要讓mybatis使用這個實現了cache接口的實現類即可。
1,Cache接口:
mybatis默認的實現類是:
2,加入需要整合的ehcache包
3,在mapper.xml配置文件中加入配置整合ehcache
4,在classpath下配置ehcache.xml加入ehcache的配置文件
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
<diskStore path="E:\develop\ehcache" />
<defaultCache
maxElementsInMemory="1000"
maxElementsOnDisk="10000000"
eternal="false"
overflowToDisk="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
</defaultCache>
</ehcache>
5,照常測試緩存是否起作用。
二級緩存應用場景總結:
對於訪問多的查詢請求且用戶對查詢結果實時性要求不高,此時可採用mybatis二級緩存技術降低數據庫訪問量,提高訪問速度,業務場景比如:耗時較高的統計分析sql、電話賬單查詢sql等。
實現方法如下:通過設置刷新間隔時間,由mybatis每隔一段時間自動清空緩存,根據數據變化頻率設置緩存刷新間隔flushInterval,比如設置爲30分鐘、60分鐘、24小時等,根據需求而定。
二級緩存使用侷限性:
mybatis二級緩存對細粒度的數據級別的緩存實現不好,比如如下需求:對商品信息進行緩存,由於商品信息查詢訪問量大,但是要求用戶每次都能查詢最新的商品信息,此時如果使用mybatis的二級緩存就無法實現當一個商品變化時只刷新該商品的緩存信息而不刷新其它商品的信息,因爲mybaits的二級緩存區域以mapper爲單位劃分,當一個商品信息變化會將所有商品信息的緩存數據全部清空。解決此類問題需要在業務層根據需求對數據有針對性緩存。