MyBatis學習(四)
延遲加載
1、延遲加載:先從單表查詢、需要時再從關聯表去關聯查詢,大大提高 數據庫性能,因為查詢單表要比關聯查詢多張表速度要快。
2、如果查詢角色並且關聯查詢動漫信息。如果先查詢角色信息即可滿足要求,當我們需要查詢動漫信息時再查詢動漫信息。把對動漫信息的按需去查詢就是延遲加載。
3、resultMap可以實現高級映射(使用association、collection實現一對一及一對多映射),association、collection具備延遲加載功能。
4、使用association實現延遲加載
- 映射文件
<?xml version="1.0" encoding="UTF-8"?>
需要定義兩個mapper的方法對應的statement。
a、只查詢角色信息
SELECT * FROM t_role
在查詢角色的statement中使用association去延遲加載(執行)下邊的satatement(關聯查詢動漫信息)
b、關聯查詢動漫信息
通過上邊查詢到的角色信息中comic_id去關聯查詢動漫信息,使用配置文件中的findComicById
上邊先去執行findRoleComicsByLazyLoading,當需要去查詢動漫的時候再去執行findComicById,通過resultMap的定義將延遲加載執行配置起來,
使用association中的select指定延遲加載去執行的statement的id。
-
接口類
package ecut.association.mapper; import java.util.List; import ecut.association.po.Comic; import ecut.association.po.Role; import ecut.association.po.RoleComic; public interface RoleMapperComic {
//根據id查詢動漫
public Comic findComicById(Integer id) throws Exception ; //查詢角色信息,關聯查詢所屬動漫信息,使用resultMap和延遲加載 public List<Role> findRoleComicsByLazyLoading() throws Exception ; } - 配置文件
<!-- 全局配置參數,需要時再設置 --> <settings> <!-- 打開延遲加載 的開關 --> <setting name="lazyLoadingEnabled" value="true"/> <!-- 將積極加載改為消極加載即按需要加載 --> <setting name="aggressiveLazyLoading" value="false"/> </settings>
在配置文件中開啟延遲查詢(默認false),並將積極加載改為消極加載(默認true)
- 測試類
package ecut.association.test; import java.io.IOException; import java.io.InputStream; import java.util.List; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import org.junit.Before; import org.junit.Test; import ecut.association.mapper.RoleMapperComic; import ecut.association.po.Comic; import ecut.association.po.Role; import ecut.association.po.RoleComic; public class ComicMapperTest { private SqlSessionFactory factory; @Before public void init() throws IOException { String resource = "ecut/association/mybaits-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactoryBuilder builer =new SqlSessionFactoryBuilder(); factory = builer.build(inputStream); } @Test public void testFindRoleComicsByLazyLoading() throws Exception { SqlSession session = factory.openSession(); RoleMapperComic mapper = session.getMapper(RoleMapperComic.class); List<Role> roleComics = mapper.findRoleComicsByLazyLoading(); //執行getComic()去查詢動漫信息,這裏實現按需懶加載 System.out.println(roleComics); } }
執行上邊mapper方法(findRoleComicsByLazyLoading),內部去調用映射文件中的findComicById只查詢角色信息(單表)。
在程序中去遍歷上一步驟查詢出的List<Role>,當我們調用roleComics中的getComic方法時,開始進行延遲加載。
延遲加載,去調用映射文件中findComicById這個方法獲取動漫信息。
- 運行結果
DEBUG [main] - ==> Preparing: SELECT * FROM t_role DEBUG [main] - ==> Parameters: DEBUG [main] - <== Total: 7 DEBUG [main] - ==> Preparing: SELECT * FROM t_comic WHERE id = ? DEBUG [main] - ==> Parameters: 1(Integer) DEBUG [main] - <== Total: 1 DEBUG [main] - ==> Preparing: SELECT * FROM t_comic WHERE id = ? DEBUG [main] - ==> Parameters: 2(Integer) DEBUG [main] - <== Total: 1 DEBUG [main] - ==> Preparing: SELECT * FROM t_comic WHERE id = ? DEBUG [main] - ==> Parameters: 3(Integer) DEBUG [main] - <== Total: 1 [ 角色信息:id =1,roleName =Saber, note = fate stay night, comicId = 1;動漫信息:id=1,comicName=null,remark=null, 角色信息:id =2,roleName =鳴人, note = 火影忍者, comicId = 2;動漫信息:id=2,comicName=null,remark=null, 角色信息:id =3,roleName =佐助, note = 火影忍者, comicId = 2;動漫信息:id=2,comicName=null,remark=null, 角色信息:id =4,roleName =赤丸, note = 火影忍者, comicId = 2;動漫信息:id=2,comicName=null,remark=null, 角色信息:id =5,roleName =兜, note = 火影忍者, comicId = 2;動漫信息:id=2,comicName=null,remark=null, 角色信息:id =6,roleName =鹿丸, note = 火影忍者, comicId = 2;動漫信息:id=2,comicName=null,remark=null, 角色信息:id =7,roleName =薩博, note = 海賊王, comicId = 3;動漫信息:id=3,comicName=null,remark=null]
先查詢角色表然後根據comi_id去查詢動漫表,查詢了三次因為有三個不同的comic_id,相同的comic_id不會調用查詢因為會存在緩存。
4、不使用mybatis提供的association及collection中的延遲加載功能,如何實現延遲加載??
- 定義兩個mapper方法:查詢角色列表、根據動漫id查詢動漫信息
- 先去查詢第一個mapper方法,獲取角色信息列表
- 在程序中(service),按需去調用第二個mapper方法去查詢動漫信息。
- 總之:使用延遲加載方法,先去查詢簡單的sql(最好單表,也可以關聯查詢),再去按需要加載關聯查詢的其它信息。
查詢緩存
1、mybatis提供查詢緩存,用於減輕數據壓力,提高數據庫性能。
2、mybaits提供一級緩存,和二級緩存。
- 一級緩存是SqlSession級別的緩存。在操作數據庫時需要構造 sqlSession對象,在對象中有一個數據結構(HashMap)用於存儲緩存數據。不同的sqlSession之間的緩存數據區域(HashMap)是互相不影響的。
- 二級緩存是mapper級別的緩存,多個SqlSession去操作同一個Mapper的sql語句,多個SqlSession可以共用二級緩存,二級緩存是跨SqlSession的。
3、作用:如果緩存中有數據就不用從數據庫中獲取,大大提高系統性能。
一級緩存
1、工作原理
- 第一次發起查詢動漫id為1的動漫信息,先去找緩存中是否有id為1的動漫信息,如果沒有,從數據庫查詢動漫信息。得到動漫信息,將動漫信息存儲到一級緩存中。
- 如果sqlSession去執行commit操作(執行插入、更新、刪除),清空SqlSession中的一級緩存,這樣做的目的為了讓緩存中存儲的是最新的信息,避免臟讀。
- 第二次發起查詢動漫id為1的動漫信息,先去找緩存中是否有id為1的動漫信息,緩存中有,直接從緩存中獲取動漫信息。
2、測試案例
mybatis默認支持一級緩存,不需要在配置文件去配置。
package ecut.association.test; import java.io.IOException; import java.io.InputStream; import java.util.List; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import org.junit.Before; import org.junit.Test; import ecut.association.mapper.RoleMapperComic; import ecut.association.po.Comic; import ecut.association.po.Role; import ecut.association.po.RoleComic; public class RoleMapperComicTest { private SqlSessionFactory factory; @Before public void init() throws IOException { String resource = "ecut/association/mybaits-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactoryBuilder builer =new SqlSessionFactoryBuilder(); factory = builer.build(inputStream); } // 一級緩存測試 @Test public void testCache1() throws Exception { SqlSession session = factory.openSession();// 創建代理對象 RoleMapperComic mapper = session.getMapper(RoleMapperComic.class); // 下邊查詢使用一個SqlSession // 第一次發起請求,查詢id為1的動漫 Comic comic1 = mapper.findComicById(1); System.out.println("動漫信息:id="+comic1.getId()+",comicName="+comic1.getComicName()+",remark="+comic1.getRemark()); // 如果session去執行commit操作(執行插入、更新、刪除),清空SqlSession中的一級緩存,這樣做的目的為了讓緩存中存儲的是最新的信息,避免臟讀。 /*// 更新comic1的信息 comic1.setComicName("fate"); mapper.updateComic(comic1); //執行commit操作去清空緩存 session.commit();*/ // 第二次發起請求,查詢id為1的動漫 Comic comic2 = mapper.findComicById(1); System.out.println("動漫信息:id="+comic2.getId()+",comicName="+comic2.getComicName()+",remark="+comic2.getRemark()); session.close(); } }
第二次查詢直接從緩存中獲取沒有查詢數據庫,若進行了update操作緩存中的數據被清空,第二次查詢需要重新查詢數據庫
3、應用場景
正式開發,是將mybatis和spring進行整合開發,事務控制在service中。一個service方法中包括 很多mapper方法調用。
二級緩存
1、工作原理
- 首先開啟mybatis的二級緩存。
- sqlSession1去查詢動漫id為1的動漫信息,查詢到動漫信息會將查詢數據存儲到二級緩存中。
- 如果SqlSession3去執行相同 mapper下sql,執行commit提交,清空該 mapper下的二級緩存區域的數據。
- sqlSession2去查詢動漫id為1的動漫信息,去緩存中找是否存在數據,如果存在直接從緩存中取出數據。
- 二級緩存與一級緩存區別,二級緩存的範圍更大,多個sqlSession可以共享一個UserMapper的二級緩存區域。
- RoleMapperComic有一個二級緩存區域(按namespace分) ,其它mapper也有自己的二級緩存區域(按namespace分)。
- 每一個namespace的mapper都有一個二緩存區域,兩個mapper的namespace如果相同,這兩個mapper執行sql查詢到數據將存在相同 的二級緩存區域中。
2、測試案例
- 配置文件中開啟二級緩存
<!-- 全局配置參數,需要時再設置 --> <settings> <!-- 打開延遲加載 的開關 --> <setting name="lazyLoadingEnabled" value="true"/> <!-- 將積極加載改為消極加載即按需要加載 --> <setting name="aggressiveLazyLoading" value="false"/> <!-- 開啟二級緩存 --> <setting name="cacheEnabled" value="true"/> </settings>
mybaits的二級緩存是mapper範圍級別,除了在SqlMapConfig.xml設置二級緩存的總開關,還要在具體的mapper.xml中開啟二級緩存。
- 映射文件中開啟二級緩存
<cache />
在RoleMapperComic.xml中開啟二緩存,RoleMapperComic.xml下的sql執行完成會存儲到它的緩存區域(HashMap)。
- POJO類
package ecut.association.po; import java.io.Serializable; import java.util.List; public class Comic implements Serializable { private static final long serialVersionUID = -3674954993814032572L; private Integer id; private String comicName; private String remark; private List<Role> roles; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getComicName() { return comicName; } public void setComicName(String comicName) { this.comicName = comicName; } public String getRemark() { return remark; } public void setRemark(String remark) { this.remark = remark; } public List<Role> getRoles() { return roles; } public void setRoles(List<Role> roles) { this.roles = roles; } }
為了將緩存數據取出執行反序列化操作,因為二級緩存數據存儲介質多種多樣,不一樣在內存。
- 測試類
package ecut.association.test; import java.io.IOException; import java.io.InputStream; import java.util.List; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import org.junit.Before; import org.junit.Test; import ecut.association.mapper.RoleMapperComic; import ecut.association.po.Comic; import ecut.association.po.Role; import ecut.association.po.RoleComic; public class RoleMapperComicTest { private SqlSessionFactory factory; @Before public void init() throws IOException { String resource = "ecut/association/mybaits-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactoryBuilder builer =new SqlSessionFactoryBuilder(); factory = builer.build(inputStream); } // 二級緩存測試 @Test public void testCache2() throws Exception { SqlSession session1 = factory.openSession(); SqlSession session2 = factory.openSession(); SqlSession session3 = factory.openSession(); // 創建代理對象 RoleMapperComic mapper1 = session1.getMapper(RoleMapperComic.class); // 第一次發起請求,查詢id為1的動漫 Comic comic1 = mapper1.findComicById(1); System.out.println("動漫信息:id="+comic1.getId()+",comicName="+comic1.getComicName()+",remark="+comic1.getRemark()); //這裏執行關閉操作,將session中的數據寫到二級緩存區域 session1.close(); //使用session3執行commit()操作 RoleMapperComic mapper3 = session3.getMapper(RoleMapperComic.class); Comic comic = mapper3.findComicById(1); comic.setComicName("fate"); mapper3.updateComic(comic); //執行提交,清空RoleMapperComic下邊的二級緩存 session3.commit(); session3.close(); RoleMapperComic mapper2 = session2.getMapper(RoleMapperComic.class); // 第二次發起請求,查詢id為1的動漫 Comic comic2 = mapper2.findComicById(1); System.out.println("動漫信息:id="+comic2.getId()+",comicName="+comic2.getComicName()+",remark="+comic2.getRemark()); session2.close(); } }
即使session關閉也可以在二級緩存中獲取數據,無需查詢數據庫。
3、useCache和刷新緩存的配置
- 在statement中設置useCache=false可以禁用當前select語句的二級緩存,即每次查詢都會發出sql去查詢,默認情況是true,即該sql使用二級緩存。
<select id="findComicById" parameterType="java.lang.Integer" resultMap="ComicResultMap" useCache="false"> SELECT * FROM t_comic WHERE id = #{value} </select>
總結:針對每次查詢都需要最新的數據sql,要設置成useCache=false,禁用二級緩存。
-
在mapper的同一個namespace中,如果有其它insert、update、delete操作數據後需要刷新緩存,如果不執行刷新緩存會出現臟讀。設置statement配置中的flushCache="true" 屬性,默認情況下為true即刷新緩存,如果改成false則不會刷新。使用緩存時如果手動修改數據庫表中的查詢數據會出現臟讀。
<update id="updateComic" parameterType="Comic" flushCache="true"> update t_comic set comic_name = #{comicName} where id =#{id} </update>
總結:一般下執行完commit操作都需要刷新緩存,flushCache=true表示刷新緩存,這樣可以避免數據庫臟讀。
4、二級緩存應用場景
- 對於訪問多的查詢請求且用戶對查詢結果實時性要求不高,此時可采用mybatis二級緩存技術降低數據庫訪問量,提高訪問速度,業務場景比如:耗時較高的統計分析sql、電話賬單查詢sql等。
- 實現方法如下:通過設置刷新間隔時間,由mybatis每隔一段時間自動清空緩存,根據數據變化頻率設置緩存刷新間隔flushInterval,比如設置為30分鐘、60分鐘、24小時等,根據需求而定。
5、二級緩存的局限性
- mybatis二級緩存對細粒度的數據級別的緩存實現不好,比如如下需求:對商品信息進行緩存,由於商品信息查詢訪問量大,但是要求用戶每次都能查詢最新的商品信息,此時如果使用mybatis的二級緩存就無法實現當一個商品變化時只刷新該商品的緩存信息而不刷新其它商品的信息,因為mybaits的二級緩存區域以mapper為單位劃分,當一個商品信息變化會將所有商品信息的緩存數據全部清空。解決此類問題需要在業務層根據需求對數據有針對性緩存。
MyBaitis整合Ehcache
1、分布式緩存
不使用分布緩存,緩存的數據在各各服務單獨存儲,不方便系統 開發。所以要使用分布式緩存對緩存數據進行集中管理。
mybatis無法實現分布式緩存,需要和其它分布式緩存框架進行整合。
2、整合方法
- mybatis提供了一個cache接口,如果要實現自己的緩存邏輯,實現cache接口開發即可。
- mybatis和ehcache整合,mybatis和ehcache整合包中提供了一個cache接口的實現類。
- 接口完整接口名org.apache.ibatis.cache.Cache
3、整合步驟(參照官網配置http://www.mybatis.org/ehcache-cache/)
- 增加jar包,下載地址https://github.com/mybatis/ehcache-cache/releases/download/mybatis-ehcache-1.0.3/mybatis-ehcache-1.0.3.zip
- 整合ehcache
<!-- 開啟本mapper的namespace下的二緩存 type:指定cache接口的實現類的類型,mybatis默認使用PerpetualCache 要和ehcache整合,需要配置type為ehcache實現cache接口的類型 --> <cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
配置mapper中cache中的type為ehcache對cache接口的實現類型。
- 加入ehcache配置文件
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE ehcache > <ehcache> <!-- 存儲位置 --> <diskStore path="java.io.tmpdir"/> <!-- 默認的cache配置 --> <defaultCache maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="true" /> </ehcache>
在classpath下配置ehcache.xml
- 遇到的問題:使用ehcache-3.5.2.jar包時會拋出java.lang.NoClassDefFoundError: net/sf/ehcache/Ehcache
的異常,是因為jar包添加錯誤,應該使用ehcache-core-2.10.3.jar
轉載請於明顯處標明出處
https://www.cnblogs.com/AmyZheng/p/9382573.html
MyBatis學習(四)