摘要:相關閱讀通過項目逐步深入了解一通過項目逐步深入了解二通過項目逐步深入了解三本項目所有代碼及文檔都托管在地址延遲加載什么是延遲加載可以實現高級映射使用實現一對一及一對多映射,具備延遲加載功能。一級緩存是級別的緩存。
相關閱讀:
1、通過項目逐步深入了解Mybatis<一>
2、通過項目逐步深入了解Mybatis<二>
3、通過項目逐步深入了解Mybatis<三>
本項目所有代碼及文檔都托管在 Github地址:https://github.com/zhisheng17/mybatis
延遲加載 什么是延遲加載?resultMap可以實現高級映射(使用association、collection實現一對一及一對多映射),association、collection具備延遲加載功能。
需求:
如果查詢訂單并且關聯查詢用戶信息。如果先查詢訂單信息即可滿足要求,當我們需要查詢用戶信息時再查詢用戶信息。把對用戶信息的按需去查詢就是延遲加載。
延遲加載:先從單表查詢、需要時再從關聯表去關聯查詢,大大提高 數據庫性能,因為查詢單表要比關聯查詢多張表速度要快。
打開延遲加載開關在mybatis核心配置文件中配置:
lazyLoadingEnabled、aggressiveLazyLoading
設置項 | 描述 | 允許值 | 默認值 |
---|---|---|---|
lazyLoadingEnabled | 全局性設置懶加載。如果設為‘false’,則所有相關聯的都會被初始化加載。 | true false | false |
aggressiveLazyLoading | 當設置為‘true’的時候,懶加載的對象可能被任何懶屬性全部加載。否則,每個屬性都按需加載。 | true false | true |
使用 association 實現延遲加載
需求:查詢訂單并且關聯查詢用戶信息
Mapper.xml需要定義兩個 mapper 的方法對應的 statement。
1、只查詢訂單信息
SQL 語句: select * from orders
在查詢訂單的 statement 中使用 association 去延遲加載(執行)下邊的 statement (關聯查詢用戶信息)
2、關聯查詢用戶信息
通過上面查詢訂單信息中的 user_id 來關聯查詢用戶信息。使用 UserMapper.xml 中的 findUserById
SQL語句:select * from user where id = user_id
上邊先去執行 findOrdersUserLazyLoading,當需要去查詢用戶的時候再去執行 findUserById ,通過 resultMap的定義將延遲加載執行配置起來。也就是通過 resultMap 去加載 UserMapper.xml 文件中的 select = findUserById
延遲加載的 resultMapOrderMapperCustom.java
public List測試代碼:findOrdersUserLazyLoading() throws Exception;
@Test public void testFindOrdersUserLazyLoading() throws Exception { SqlSession sqlSession = sqlSessionFactory.openSession(); //創建OrdersMapperCustom對象,mybatis自動生成代理對象 OrdersMapperCustom ordersMapperCustom = sqlSession.getMapper(OrdersMapperCustom.class); //查詢訂單信息 List測試結果:list = ordersMapperCustom.findOrdersUserLazyLoading(); //遍歷所查詢的的訂單信息 for (Orders orders : list) { //查詢用戶信息 User user = orders.getUser(); System.out.println(user); } sqlSession.close(); }
整個延遲加載的思路:
1、執行上邊mapper方法(findOrdersUserLazyLoading),內部去調用cn.zhisheng.mybatis.mapper.OrdersMapperCustom 中的 findOrdersUserLazyLoading 只查詢 orders 信息(單表)。
2、在程序中去遍歷上一步驟查詢出的 List
3、延遲加載,去調用 UserMapper.xml 中 findUserbyId 這個方法獲取用戶信息。
思考:不使用 mybatis 提供的 association 及 collection 中的延遲加載功能,如何實現延遲加載??
實現方法如下:
定義兩個mapper方法:
1、查詢訂單列表
2、根據用戶id查詢用戶信息
實現思路:
先去查詢第一個mapper方法,獲取訂單信息列表
在程序中(service),按需去調用第二個mapper方法去查詢用戶信息。
總之:
使用延遲加載方法,先去查詢 簡單的 sql(最好單表,也可以關聯查詢),再去按需要加載關聯查詢的其它信息。
一對多延遲加載上面的那個案例是一對一延遲加載,那么如果我們想一對多進行延遲加載呢,其實也是很簡單的。
一對多延遲加載的方法同一對一延遲加載,在collection標簽中配置select內容。
延遲加載總結:作用:
當需要查詢關聯信息時再去數據庫查詢,默認不去關聯查詢,提高數據庫性能。
只有使用resultMap支持延遲加載設置。
場合:
查詢緩存 什么是查詢緩存?當只有部分記錄需要關聯查詢其它信息時,此時可按需延遲加載,需要關聯查詢時再向數據庫發出sql,以提高數據庫性能。
當全部需要關聯查詢信息時,此時不用延遲加載,直接將關聯查詢信息全部返回即可,可使用resultType或resultMap完成映射。
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對象,mybatis自動生成代理對象 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); //查詢使用的是同一個session //第一次發起請求,查詢Id 為1的用戶信息 User user1 = userMapper.findUserById(1); System.out.println(user1); //第二次發起請求,查詢Id 為1的用戶信息 User user2 = userMapper.findUserById(1); System.out.println(user2); sqlSession.close(); }
通過結果可以看出第二次沒有發出sql查詢請求,
所以我們需要在中間執行 commit 操作
//如果sqlSession去執行commit操作(執行插入、更新、刪除), // 清空SqlSession中的一級緩存,這樣做的目的為了讓緩存中存儲的是最新的信息,避免臟讀。 //更新user1的信息, user1.setUsername("李飛"); //user1.setSex("男"); //user1.setAddress("北京"); userMapper.updateUserById(user1); //提交事務,才會去清空緩存 sqlSession.commit();
測試
一級緩存應用
正式開發,是將 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中開啟二級緩存
在 SqlMapConfig.xml 開啟二級開關
然后在你的 Mapper 映射文件中添加一行:
調用 pojo 類實現序列化接口:
二級緩存需要查詢結果映射的pojo對象實現java.io.Serializable接口實現序列化和反序列化操作(因為二級緩存數據存儲介質多種多樣,在內存不一樣),注意如果存在父類、成員pojo都需要實現序列化接口。
public class Orders implements Serializable public class User implements Serializable
測試
//二級緩存測試 @Test public void testCache2() throws Exception { SqlSession sqlSession1 = sqlSessionFactory.openSession(); SqlSession sqlSession2 = sqlSessionFactory.openSession(); SqlSession sqlSession3 = sqlSessionFactory.openSession(); //創建UserMapper對象,mybatis自動生成代理對象 UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class); //sqlSession1 執行查詢 寫入緩存(第一次查詢請求) User user1 = userMapper1.findUserById(1); System.out.println(user1); sqlSession1.close(); //sqlSession3 執行提交 清空緩存 UserMapper userMapper3 = sqlSession3.getMapper(UserMapper.class); User user3 = userMapper3.findUserById(1); user3.setSex("女"); user3.setAddress("山東濟南"); user3.setUsername("崔建"); userMapper3.updateUserById(user3); //提交事務,清空緩存 sqlSession3.commit(); sqlSession3.close(); //sqlSession2 執行查詢(第二次查詢請求) UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class); User user2 = userMapper2.findUserById(1); System.out.println(user2); sqlSession2.close(); }
結果:
useCache 配置
在 statement 中設置 useCache=false 可以禁用當前 select 語句的二級緩存,即每次查詢都會發出sql去查詢,默認情況是true,即該sql使用二級緩存。
總結:針對每次查詢都需要最新的數據sql,要設置成useCache=false,禁用二級緩存。
刷新緩存(清空緩存)
在mapper的同一個namespace中,如果有其它insert、update、delete操作數據后需要刷新緩存,如果不執行刷新緩存會出現臟讀。
設置statement配置中的flushCache="true" 屬性,默認情況下為true即刷新緩存,如果改成false則不會刷新。使用緩存時如果手動修改數據庫表中的查詢數據會出現臟讀。
如下:
一般下執行完commit操作都需要刷新緩存,flushCache=true表示刷新緩存,這樣可以避免數據庫臟讀。
Mybatis Cache參數flushInterval(刷新間隔)可以被設置為任意的正整數,而且它們代表一個合理的毫秒形式的時間段。默認情況是不設置,也就是沒有刷新間隔,緩存僅僅調用語句時刷新。
size(引用數目)可以被設置為任意正整數,要記住你緩存的對象數目和你運行環境的可用內存資源數目。默認值是1024。
readOnly(只讀)屬性可以被設置為true或false。只讀的緩存會給所有調用者返回緩存對象的相同實例。因此這些對象不能被修改。這提供了很重要的性能優勢??勺x寫的緩存會返回緩存對象的拷貝(通過序列化)。這會慢一些,但是安全,因此默認是false。
如下例子:
這個更高級的配置創建了一個 FIFO 緩存,并每隔 60 秒刷新,存數結果對象或列表的 512 個引用,而且返回的對象被認為是只讀的,因此在不同線程中的調用者之間修改它們會導致沖突??捎玫氖栈夭呗杂? 默認的是 LRU:
LRU – 最近最少使用的:移除最長時間不被使用的對象。
FIFO – 先進先出:按對象進入緩存的順序來移除它們。
SOFT – 軟引用:移除基于垃圾回收器狀態和軟引用規則的對象。
WEAK – 弱引用:更積極地移除基于垃圾收集器狀態和弱引用規則的對象。
Mybatis 整合 ehcacheehcache 是一個分布式緩存框架。
分布緩存
我們系統為了提高系統并發,性能、一般對系統進行分布式部署(集群部署方式)
不使用分布緩存,緩存的數據在各各服務多帶帶存儲,不方便系統 開發。所以要使用分布式緩存對緩存數據進行集中管理。
mybatis無法實現分布式緩存,需要和其它分布式緩存框架進行整合。
整合方法
mybatis 提供了一個二級緩存 cache 接口(org.apache.ibatis.cache 下的 Cache),如果要實現自己的緩存邏輯,實現cache接口開發即可。
import java.util.concurrent.locks.ReadWriteLock; public interface Cache { String getId(); void putObject(Object var1, Object var2); Object getObject(Object var1); Object removeObject(Object var1); void clear(); int getSize(); ReadWriteLock getReadWriteLock(); }
mybatis和ehcache整合,mybatis 和 ehcache 整合包中提供了一個 cache 接口的實現類(org.apache.ibatis.cache.impl 下的 PerpetualCache)。
package org.apache.ibatis.cache.impl; import java.util.HashMap; import java.util.Map; import java.util.concurrent.locks.ReadWriteLock; import org.apache.ibatis.cache.Cache; import org.apache.ibatis.cache.CacheException; public class PerpetualCache implements Cache { private String id; private Map
通過實現 Cache 接口可以實現 mybatis 緩存數據通過其它緩存數據庫整合,mybatis 的特長是sql操作,緩存數據的管理不是 mybatis 的特長,為了提高緩存的性能將 mybatis 和第三方的緩存數據庫整合,比如 ehcache、memcache、redis等。
引入依賴包
ehcache-core-2.6.5.jar 和 mybatis-ehcache-1.0.2.jar
引入緩存配置文件
classpath下添加:ehcache.xml
內容如下:
屬性說明:
diskStore:指定數據在磁盤中的存儲位置。
defaultCache:當借助 CacheManager.add("demoCache") 創建Cache時,EhCache 便會采用
以下屬性是必須的:
maxElementsInMemory - 在內存中緩存的element的最大數目
maxElementsOnDisk - 在磁盤上緩存的element的最大數目,若是0表示無窮大
eternal - 設定緩存的elements是否永遠不過期。如果為true,則緩存的數據始終有效,如果為false那么還要根據timeToIdleSeconds,timeToLiveSeconds判斷
overflowToDisk- 設定當內存緩存溢出的時候是否將過期的element緩存到磁盤上
以下屬性是可選的:
timeToIdleSeconds - 當緩存在EhCache中的數據前后兩次訪問的時間超過timeToIdleSeconds的屬性取值時,這些數據便會刪除,默認值是0,也就是可閑置時間無窮大
timeToLiveSeconds - 緩存element的有效生命期,默認是0.,也就是element存活時間無窮大
diskSpoolBufferSizeMB 這個參數設置DiskStore(磁盤緩存)的緩存區大小.默認是30MB.每個Cache都應該有自己的一個緩沖區.
diskPersistent- 在VM重啟的時候是否啟用磁盤保存EhCache中的數據,默認是false。
diskExpiryThreadIntervalSeconds - 磁盤緩存的清理線程運行間隔,默認是120秒。每個120s,相應的線程會進行一次EhCache中數據的清理工作
memoryStoreEvictionPolicy - 當內存緩存達到最大,有新的element加入的時候, 移除緩存中element的策略。默認是LRU(最近最少使用),可選的有LFU(最不常使用)和FIFO(先進先出)
開啟ehcache緩存
EhcacheCache 是ehcache對Cache接口的實現;修改mapper.xml文件,在cache中指定EhcacheCache。
根據需求調整緩存參數:
測試 :(這命中率就代表成功將ehcache 與 mybatis 整合了)
應用場景對于訪問多的查詢請求且用戶對查詢結果實時性要求不高,此時可采用 mybatis 二級緩存技術降低數據庫訪問量,提高訪問速度,業務場景比如:耗時較高的統計分析sql、電話賬單查詢sql等。
實現方法如下:通過設置刷新間隔時間,由 mybatis 每隔一段時間自動清空緩存,根據數據變化頻率設置緩存刷新間隔 flushInterval,比如設置為30分鐘、60分鐘、24小時等,根據需求而定。
局限性mybatis 二級緩存對細粒度的數據級別的緩存實現不好,比如如下需求:對商品信息進行緩存,由于商品信息查詢訪問量大,但是要求用戶每次都能查詢最新的商品信息,此時如果使用 mybatis 的二級緩存就無法實現當一個商品變化時只刷新該商品的緩存信息而不刷新其它商品的信息,因為 mybaits 的二級緩存區域以 mapper 為單位劃分,當一個商品信息變化會將所有商品信息的緩存數據全部清空。解決此類問題需要在業務層根據需求對數據有針對性緩存。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/66318.html
摘要:從使用到原理學習線程池關于線程池的使用,及原理分析分析角度新穎面向切面編程的基本用法基于注解的實現在軟件開發中,分散于應用中多出的功能被稱為橫切關注點如事務安全緩存等。 Java 程序媛手把手教你設計模式中的撩妹神技 -- 上篇 遇一人白首,擇一城終老,是多么美好的人生境界,她和他歷經風雨慢慢變老,回首走過的點點滴滴,依然清楚的記得當初愛情萌芽的模樣…… Java 進階面試問題列表 -...
摘要:前提好幾周沒更新博客了,對不斷支持我博客的童鞋們說聲抱歉了。熟悉我的人都知道我寫博客的時間比較早,而且堅持的時間也比較久,一直到現在也是一直保持著更新狀態。 showImg(https://segmentfault.com/img/remote/1460000014076586?w=1920&h=1080); 前提 好幾周沒更新博客了,對不斷支持我博客的童鞋們說聲:抱歉了!。自己這段時...
閱讀 1793·2023-04-25 15:51
閱讀 2502·2021-10-13 09:40
閱讀 2137·2021-09-23 11:22
閱讀 3247·2019-08-30 14:16
閱讀 2657·2019-08-26 13:35
閱讀 1853·2019-08-26 13:31
閱讀 880·2019-08-26 11:39
閱讀 2739·2019-08-26 10:33