Mybatis查询延迟加载详解及实例
1.1 启用延迟加载
Mybatis的延迟加载是针对嵌套查询而言的,是指在进行查询的时候先只查询最外层的SQL,对于内层SQL将在需要使用的时候才查询出来。Mybatis的延迟加载默认是关闭的,即默认是一次就将所有的嵌套SQL一并查了将对象所有的信息都查询出来。开启延迟加载有两种方式。
第一种是在对应的<collection>或<association>标签上指定fetchType属性值为“lazy”。如下示例中我们在查询id为selectByPrimaryKey的查询时会返回BaseResultMap,在BaseResultMap中,我们指定了属性“nodes”是一个集合类型的,而且是需要通过id为selectNodes的查询进行查询的,我们指定了该查询的fetchType为lazy,即延迟加载。
<resultMap id="BaseResultMap" type="com.elim.learn.mybatis.model.SysWfProcess"> <id column="id" jdbcType="INTEGER" property="id" /> <result column="template_id" jdbcType="INTEGER" property="templateId" /> <result column="creator" jdbcType="INTEGER" property="creator" /> <result column="create_time" jdbcType="TIMESTAMP" property="createTime" /> <collection property="nodes" column="id" ofType="com.elim.learn.mybatis.model.SysWfNode" select="selectNodes" fetchType="lazy"/> </resultMap> <resultMap id="SysWfNodeResult" type="com.elim.learn.mybatis.model.SysWfNode"> <id column="id" jdbcType="INTEGER" property="nodeId" /> <result column="process_id" jdbcType="INTEGER" property="processId" /> <result column="node_code" jdbcType="VARCHAR" property="nodeCode" /> <result column="node_name" jdbcType="VARCHAR" property="nodeName" /> </resultMap> <select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultMap="BaseResultMap"> select <include refid="Base_Column_List" /> from sys_wf_process where id = #{id,jdbcType=INTEGER} </select> <select id="selectNodes" resultMap="SysWfNodeResult"> select id, process_id, node_code, node_name from sys_wf_node where process_id=#{id} </select>
第二种是开启全局的延迟加载。通过在Mybatis的配置文件的<settings>标签下加上如下配置可开启全局的延迟加载。开启了全局的延迟加载后我们就无需再在各个嵌套的子查询上配置延迟加载了,如果有某一个嵌套的子查询是不需要延迟加载的,可以设置其fetchType=”eager”。设置在嵌套查询上的fetchType可以覆盖全局的延迟加载设置。
<setting name="lazyLoadingEnabled" value="true"/>
1.2 分析
Mybatis的查询结果是由ResultSetHandler接口的handleResultSets()方法处理的。ResultSetHandler接口只有一个实现,DefaultResultSetHandler。有兴趣的朋友可以去看一下它的源码,看一下它是如何处理结果集的。对于本文的主题,延迟加载相关的一个核心的方法就是如下这个创建返回结果对象的方法。
private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException { final List<Class<? constructorArgTypes = new ArrayList<Class<?(); final List<Object> constructorArgs = new ArrayList<Object>(); final Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix); if (resultObject != null && !typeHandlerRegistry.hasTypeHandler(resultMap.getType())) { final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings(); for (ResultMapping propertyMapping : propertyMappings) { // issue gcode #109 && issue #149 if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) { return configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs); } } } return resultObject; }
在上面方法中我们可以看到Mybatis先是根据正常情况创建一个返回类型对应的对象。当我们的ResultMap是包含子查询的时候,其会在我们正常返回类型对象的基础上创建对应的代理对象。对,你没有看错,就是我们的直接结果是代理对象,而不是子查询对应的属性是代理对象。默认是基于JavassistProxyFactory类创建的代理对象。可以通过Mybatis的全局配置proxyFactory来更改,可选值是CGLIB和JAVASSIST,默认是后者。需要使用CGLIB代理时注意加入CGLIB的包。
<setting name="proxyFactory" value="CGLIB"/>
回过头来看我们之前的那个延迟加载的配置,我们的一个查询返回的是SysWfProcess类型的对象,其有一个SysWfNode集合类型的nodes属性,nodes属性是通过一个子查询查出来的,而且是延迟加载。这个时候我们来进行以下测试。
@Test public void testLazyLoad1() { SysWfProcessMapper mapper = this.session.getMapper(SysWfProcessMapper.class); SysWfProcess process = mapper.selectByPrimaryKey(1); System.out.println(process.getClass()); }
这个时候你会发现,上面的测试代码的输出结果是一个代理类,而不是我们自己的com.elim.learn.mybatis.model.SysWfProcess类型。另外如果你启用了日志输出,并且是打印的DEBUG日志,你会看到Mybatis是发了两条SQL进行查询的。
2016-12-23 15:43:21,131 DEBUG [main] (BaseJdbcLogger.java:145) - ==> Preparing: select id, template_id, creator, create_time from sys_wf_process where id = "htmlcode"><setting name="aggressiveLazyLoading" value="fasle"/>1.4 lazyLoadTriggerMethods
那如果我们设置了aggressiveLazyLoading=”false”,但又希望在调用某些方法之前把所有的延迟对象都从数据库加载出来,怎么办呢?这个时候我们可以通过lazyLoadTriggerMethods参数来指定需要加载延迟对象的方法调用。默认是equals、clone、hashCode和toString,也就是说我们在调用代理对象的这些方法之前就会把延迟加载对象从数据库加载出来。
<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString" />Mybatis延迟加载生成的代理对象的代理过程,可以参考ProxyFactory的创建代理对象的过程,以下是基于Javassist创建的代理对象的代理过程,基于CGLIB的代理也是类似的。从下面的代码我们可以看到Mybatis的代理对象需要从数据库加载延迟对象时是在目标方法被调用以前发生的,这就可以保证我们的目标方法被调用时延迟加载的对象已经从数据库中加载出来了。
@Override public Object invoke(Object enhanced, Method method, Method methodProxy, Object[] args) throws Throwable { final String methodName = method.getName(); try { synchronized (lazyLoader) { if (WRITE_REPLACE_METHOD.equals(methodName)) { Object original = null; if (constructorArgTypes.isEmpty()) { original = objectFactory.create(type); } else { original = objectFactory.create(type, constructorArgTypes, constructorArgs); } PropertyCopier.copyBeanProperties(type, enhanced, original); if (lazyLoader.size() > 0) { return new JavassistSerialStateHolder(original, lazyLoader.getProperties(), objectFactory, constructorArgTypes, constructorArgs); } else { return original; } } else { if (lazyLoader.size() > 0 && !FINALIZE_METHOD.equals(methodName)) { if (aggressive || lazyLoadTriggerMethods.contains(methodName)) { lazyLoader.loadAll(); } else if (PropertyNamer.isProperty(methodName)) { final String property = PropertyNamer.methodToProperty(methodName); if (lazyLoader.hasLoader(property)) { lazyLoader.load(property); } } } } } return methodProxy.invoke(enhanced, args); } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } } }本文是介绍的都是基于<collection>这种关联,其实<association>关联的对象的延迟加载也是一样的,它们的默认策略也是一样的。
感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!
免责声明:本站资源来自互联网收集,仅供用于学习和交流,请遵循相关法律法规,本站一切资源不代表本站立场,如有侵权、后门、不妥请联系本站删除!
更新日志
- 刘文正《流金三十年》[6N纯银镀膜][低速原抓WAV+CUE]
- 赵传.1994-精挑细选精选集【滚石】【WAV+CUE】
- 郑亚弦.2024-隔壁包厢603(EP)【发现梦想】【FLAC分轨】
- 文章.2004-被遗忘的时光【华博音乐】【WAV+CUE】
- 群星《青葱韶歌》原力计划·毕业季企划合辑[FLAC+分轨][661M]
- 群星《抖烧 DSD》抖音神曲 [WAV分轨][992M]
- 庾澄庆《哈林天堂》索尼音乐[WAV+CUE][1G]
- 英雄联盟全球总决赛多久打一次 全球总决赛举办频率介绍
- 第二届老头杯什么时候开始选人 第二届老头杯选人时间介绍
- 英雄联盟第二届老头杯什么时候开始 老头杯s2赛程时间队伍名单汇总
- AI赋能卓越显示技术共筑数字未来:三星显示器产品矩阵亮相2024进博会
- 技术剖析:天玑9400如何打造移动最强GPU和游戏体验?
- 顶级装备 实力登顶:三星显示器双十一焕新升级最后冲刺
- 陈影《绝色靓声》WAV+CUE
- 龚玥《禅是一枝花(6N纯银SQCD)》原抓WAV+CUE