一文了解mybatis的延遲加載
本文主要介紹下mybatis的延遲加載,從原理上介紹下怎么使用、有什么好處能規(guī)避什么問題。延遲加載一般用于級聯(lián)查詢(級聯(lián)查詢可以將主表不能直接查詢的數(shù)據(jù)使用自定義映射規(guī)則調(diào)用字表來查,主查詢查完之后通過某個(gè)column列或多個(gè)列將查詢結(jié)果傳遞給子查詢,子查詢再根據(jù)主查詢傳遞的參數(shù)進(jìn)行查詢,最后將子查詢結(jié)果進(jìn)行映射)。mybatis的懶加載是通過創(chuàng)建代理對象來實(shí)現(xiàn)的,只有當(dāng)調(diào)用getter等方法的時(shí)候才會去查詢子查詢,查詢后完成設(shè)值再獲取值。
1. 什么時(shí)候會創(chuàng)建代理對象
private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException { this.useConstructorMappings = false; // reset previous mapping result final List<Class<?>> constructorArgTypes = new ArrayList<>(); final List<Object> constructorArgs = new ArrayList<>(); // 創(chuàng)建result接收對象 Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix); if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) { // 處理其他屬性properties final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings(); for (ResultMapping propertyMapping : propertyMappings) { // issue gcode #109 && issue #149 創(chuàng)建代理 if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) { resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs); break; } } } // 使用有參構(gòu)造函數(shù)創(chuàng)建了對象 this.useConstructorMappings = resultObject != null && !constructorArgTypes.isEmpty(); // set current mapping result return resultObject; }
通過mybatis代碼propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()
發(fā)現(xiàn)只有當(dāng)存在嵌套查詢select子句和isLazy=true的時(shí)候才會創(chuàng)建代理,那么isLazy=true是什么條件,從創(chuàng)建ResultMapping的代碼中可以看到boolean lazy = "lazy".equals(context.getStringAttribute("fetchType", configuration.isLazyLoadingEnabled() ? "lazy" : "eager"));
只有手動(dòng)設(shè)置fetchType=lazy或者全局設(shè)置configuration的lazyLoadingEnabled=true,兩者缺一不可。
關(guān)于代理是通過Javassist創(chuàng)建的,下面有一個(gè)簡單的例子
public class HelloMethodHandler implements MethodHandler { private Object target; public HelloMethodHandler(Object o) { this.target = o; } @Override public Object invoke(Object self, Method thisMethod, Method proceed, Object[] args) throws Throwable { String methodName = thisMethod.getName(); if (methodName.startsWith("get")) { System.out.println("select database...."); // 進(jìn)行sql查詢到結(jié)果并set設(shè)置值 ((Student)self).setName("monian"); } return proceed.invoke(self, args); } public static void main(String[] args) throws Exception { Student student = new Student(); ProxyFactory proxyFactory = new ProxyFactory(); proxyFactory.setSuperclass(Student.class); Constructor<Student> declaredConstructor = Student.class.getDeclaredConstructor(); Object o = proxyFactory.create(declaredConstructor.getParameterTypes(), new Object[]{}); ((Proxy)o).setHandler(new HelloMethodHandler(student)); Student proxy = (Student)o; System.out.println(proxy.getName()); } }
mybatis的原理就是通過創(chuàng)建一個(gè)代理對象,當(dāng)通過這個(gè)代理對象調(diào)用getter、is、equals、clone、toString、hashCode等方法時(shí)會調(diào)用select子查詢,然后完成設(shè)置,最后取值就像早就獲取到一樣。
2. 如何使用
public class UserDO { private Integer userId; private String username; private String password; private String nickname; private List<PermitDO> permitDOList; public UserDO() {} }
<resultMap id="BaseMap" type="org.apache.ibatis.study.entity.UserDO"> <id column="user_id" jdbcType="INTEGER" property="userId" /> <result column="username" jdbcType="VARCHAR" property="username" /> <result column="password" jdbcType="VARCHAR" property="password" /> <result column="nickname" jdbcType="VARCHAR" property="nickname"/> <collection property="permitDOList" column="user_id" select="getPermitsByUserId" fetchType="lazy"> </collection> </resultMap> <resultMap id="PermitBaseMap" type="org.apache.ibatis.study.entity.PermitDO"> <id column="id" jdbcType="INTEGER" property="id"/> <result column="code" jdbcType="VARCHAR" property="code"/> <result column="name" jdbcType="VARCHAR" property="name"/> <result column="type" jdbcType="TINYINT" property="type"/> <result column="pid" jdbcType="INTEGER" property="pid"/> </resultMap> <select id="getByUserId2" resultMap="BaseMap"> select * from user where user_id = #{userId} </select> <select id="getPermitsByUserId" resultMap="PermitBaseMap"> select p.* from user_permit up inner join permit p on up.permit_id = p.id where up.user_id = #{userId} </select>
通過fetchType=lazy指定子查詢getPermitsByUserId使用懶加載,這樣的話就不用管全局配置lazyLoadingEnabled是true還是false了。當(dāng)然這里可以直接用多表關(guān)聯(lián)查詢不使用子查詢,使用方法在上一篇文章
測試代碼
public class Test { public static void main(String[] args) throws IOException { try (InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml")) { // 構(gòu)建session工廠 DefaultSqlSessionFactory SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession sqlSession = sqlSessionFactory.openSession(); UserMapper userMapper = sqlSession.getMapper(UserMapper.class); UserDO userDO = userMapper.getByUserId2(1); System.out.println(userDO); } } }
結(jié)果如下,打了斷點(diǎn)可以看到原userDO對象已被代理并且permitDOList是null需要調(diào)用get方法才會去查詢拿到值,咳咳這邊之前直接運(yùn)行顯示是已經(jīng)把permitDOList查詢出來了,想了半天啥原因后來才發(fā)現(xiàn)println會調(diào)用userDO對象的toString方法,而toString方法也會走代理方法直接去調(diào)用子查詢的
3.延遲加載的好處
延遲加載主要能解決mybatis的N+1問題,什么是N+1問題其實(shí)叫1+N更為合理,以上面的業(yè)務(wù)例子來說就是假設(shè)一次查詢出來10000個(gè)用戶,那么還需要針對這10000個(gè)用戶使用子查詢getPermitsByUserId獲取每個(gè)用戶的權(quán)限列表,需要10000次查詢,總共10001次,真實(shí)情況下你可能并不需要每個(gè)子查詢的結(jié)果,這樣就浪費(fèi)數(shù)據(jù)庫連接資源了。如果使用延遲加載的話就相當(dāng)于不用進(jìn)行這10000次查詢,因?yàn)樗堑鹊侥阏嬲褂玫臅r(shí)候才會調(diào)用子查詢獲取結(jié)果。
以上就是一文了解mybatis的延遲加載的詳細(xì)內(nèi)容,更多關(guān)于mybatis延遲加載的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java實(shí)現(xiàn)excel大數(shù)據(jù)量導(dǎo)入
這篇文章主要為大家詳細(xì)介紹了Java實(shí)現(xiàn)excel大數(shù)據(jù)量導(dǎo)入,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-08-08使用SpringBoot注解方式處理事務(wù)回滾實(shí)現(xiàn)
這篇文章主要介紹了使用SpringBoot注解方式處理事務(wù)回滾實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08解決maven?maven.compiler.source和maven.compiler.target的坑
這篇文章主要介紹了解決maven?maven.compiler.source和maven.compiler.target的坑,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12Springboot詳解整合SpringSecurity實(shí)現(xiàn)全過程
Spring Security基于Spring開發(fā),項(xiàng)目中如果使用Springboot作為基礎(chǔ),配合Spring Security做權(quán)限更加方便,而Shiro需要和Spring進(jìn)行整合開發(fā)。因此作為spring全家桶中的Spring Security在java領(lǐng)域很常用2022-07-07java實(shí)現(xiàn)適用于安卓的文件下載線程類
本文給大家分享的是java實(shí)現(xiàn)適用于安卓的文件下載線程類的代碼,有需要的小伙伴可以參考下2015-07-07淺談使用java實(shí)現(xiàn)阿里云消息隊(duì)列簡單封裝
這篇文章主要介紹了淺談使用java實(shí)現(xiàn)阿里云消息隊(duì)列簡單封裝,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-03-03