赞
踩
延迟加载:假设存在用户、订单两张表,可以查询用户(User)及用户对应的订单(Order)列表(一对多);用户信息作为主体,而订单信息不是立即需要获取到的情况下,MyBatis提供延迟加载的策略,发送SQL执行语句时,只查询用户信息,当需要使用到订单信息时,即user.getOrderList()时,才会发送获取订单信息的SQL查询订单信息 (需要用到对应的信息时,才执行相关SQL);
MyBatis延迟加载本质上:通过动态代理的形式,创建了目标对象(User)的代理对象,拦截了对象的getting方法,在执行getting方法时,进入拦截器的invoke方法,当发现需要延迟加载时,会把之前存放好的SQL语句进行执行,并调用对象(User)调用set方法存值,然后调用对象本身的get方法取值
本文主要讨论
1.如何实现MyBatis的延迟加载功能(MyBatis默认不开启延迟加载);
2.剖析源码查看器延迟加载原理
1.1 延迟加载开启(局部)
A.MyBatis中延迟加载的实现是,将复杂的SQL语句进行拆分,分步执行从而到达延迟的目的
如下根据订单ID查询订单及对应用户 (建议:一对多,多对多通常采用延迟加载,一对一通常采用立即加载,此案例仅为了简单说明)的多表关联查询,分为两步
select * from orders o left join user u on o.uid = u.id where o.id = 1
根据i订单ID查询用户
根据第一步查出来的订单对应的uid(即两表关联时的订单表的UID)作为ID查询用户表
- SELECT * FROM orders O where id = 1
-
- SELECT * FROM user where id = #{uid}
B.增加两个对应的接口及sql编写
- <select id="findById" resultMap="orderUserResult" parameterType="int">
- SELECT * FROM orders O where id = #{id}
- </select>
-
-
- <select id="findById" parameterType="int" resultType="com.kay.pojo.User">
- select * from user where id = #{id}
- </select>
C.在orderMapper.xml中,在定义Order返回的结果集类型中<association>标签中 增加三个属性 fetchType="lazy" select="com.kay.dao.UserMapper.findById" column="uid" fetchType 可以设置为lazy(懒加载)、eager(立即加载 默认此加载模式);select 为 order对象中涉及到的根据订单ID获取user对象对应的接口地址; colunm 为查询用户信息时,order表中需要传入的字段;( 属性可配置在<association> 或者 <collection>标签下)
- <resultMap id="orderUserResult" type="com.kay.pojo.Order">
- <result column="id" property="id"></result>
- <result column="ordertime" property="orderTime"></result>
- <result column="total" property="total"></result>
-
- <!-- 局部延迟加载 2 !!!!!!!! -->
- <!-- fetchType="lazy" 默认为eager select 拆分成多条SQL语句后,在 SELECT * FROM orders O where id = #{id} 后执行的另一条对应的mapper类地址
- column="uid" 当前order 表是uid与user表中字段关联 -->
- <association property="user" javaType="com.kay.pojo.User" fetchType="lazy"
- select="com.kay.dao.UserMapper.findById" column="uid">
- <result column="uid" property="id"></result>
- <result column="username" property="username"></result>
- <result column="password" property="password"></result>
- <result column="birthday" property="birthday"></result>
- </association>
- </resultMap>
D.验证 查询order及调用Order中getOrderTime不会执行查询用户信息的SQL,只有调用getUser后才发送了查询用户信息的SQL
1.2 延迟加载开启(全局) 当全局和局部同时配置了,以局部的为准
在核心配置文件中增加<setting> 配置 lazyLoadingEnabled为true
- <settings>
- <setting name="lazyLoadingEnabled" value="true"/>
- </settings>
2. 延迟加载源码剖析
在加载Mybatis核心配置文件的XMLConfigBuilder类中,向Configuration对象中赋值时,默认不开启延迟加载,触发一次延迟加载的方法包括 : equals,clone,hashCode,toString
- configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));
- configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString"));
A. ResultSetHandler的唯一实现类DefaultResultSetHandler中的createResultObject()方法创建映射后的结果对象时,根据判断当前的ResultMap中开启了lazy,通过ProxyFactory创建ResultObject的代理对象
configuration.getProxyFactory()
protected ProxyFactory proxyFactory = new JavassistProxyFactory();
=== >>> 延迟加载默认有Javassist实现
B.JavassistProxyFactory中的createProxy() 实际是调用的 EnhancedResultObjectProxyImpl.createProxy() ;
在此方法中,创建EnhancedResultObjectProxyImpl callback实例,传入 crateProxy(type, callback, constructorArgTypes, constructorArgs);中,在后者中创建代理对象,并对其设置代理执行器callback。故在调用对象方法时,调用的是EnhancedResultObjectProxyImpl中的invoke方法
- @Override
- public Object createProxy(Object target, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
- return EnhancedResultObjectProxyImpl.createProxy(target, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
- }
C.由invoke方法可见: 代理对象实例对应属性执行了set方法,此实例对象的对应属性将不再执行延迟加载;如果调用了 get 方法,则执行延迟加载
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。