延迟加载:假设存在用户、订单两张表,可以查询用户(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编写
C.在orderMapper.xml中,在定义Order返回的结果集类型中
D.验证 查询order及调用Order中getOrderTime不会执行查询用户信息的SQL,只有调用getUser后才发送了查询用户信息的SQL
1.2 延迟加载开启(全局) 当全局和局部同时配置了,以局部的为准
在核心配置文件中增加
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> constructorArgTypes, List
C.由invoke方法可见: 代理对象实例对应属性执行了set方法,此实例对象的对应属性将不再执行延迟加载;如果调用了 get 方法,则执行延迟加载