什么是延迟加载?
在开发过程中很多时候我们并不需要总是在加载⽤户信息时就⼀定要加载他的订单信息。此时就是我 们所说的延迟加载。 举个栗⼦:
在⼀对多中,当我们有⼀个⽤户,它有个100个订单 在查询⽤户的时候,要不要把关联的订单查出来? 在查询订单的时候,要不要把关联的⽤户查出来? 回答 在查询⽤户时,⽤户下的订单应该是,什么时候⽤,什么时候查询。 在查询订单时,订单所属的⽤户信息应该是随着订单⼀起查询出来。
延迟加载 就是在需要⽤到数据时才进⾏加载,不需要⽤到数据时就不加载数据。延迟加载也称懒加载。
- 优点: 先从单表查询,需要时再从关联表去关联查询,⼤⼤提⾼数据库性能,因为查询单表要⽐关联查询多张表速度要快。
- 缺点: 因为只有当需要⽤到数据时,才会进⾏数据库查询,这样在⼤批量数据查询时,因为查询⼯作也要消耗时间,所以可能造成⽤户等待时间变⻓,造成⽤户体验下降。 在多表中: ⼀对多,多对多:通常情况下采⽤延迟加载 ⼀对⼀(多对⼀):通常情况下采⽤⽴即加载
注意: 延迟加载是基于嵌套查询来实现的。
实现
局部延迟加载
在association和collection标签中都有⼀个fetchType属性,通过修改它的值,可以修改局部的加载策略。
代码语言:javascript复制<!-- 开启⼀对多 延迟加载 -->
<resultMap id="userMap" type="user">
<id column="id" property="id"></id>
<result column="username" property="username"></result>
<result column="password" property="password"></result>
<result column="birthday" property="birthday"></result>
<!--
fetchType="lazy" 懒加载策略
fetchType="eager" ⽴即加载策略
-->
<collection property="orderList" ofType="order" column="id" select="com.zjq.dao.OrderMapper.findByUid" fetchType="lazy">
</collection>
</resultMap>
<select id="findAll" resultMap="userMap">
SELECT * FROM `user`
</select>
全局延迟加载
在Mybatis的核⼼配置⽂件中可以使⽤setting标签修改全局的加载策略。
代码语言:javascript复制<settings>
<!--开启全局延迟加载功能-->
<setting name="lazyLoadingEnabled" value="true"/>
</settings>
注意:
代码语言:javascript复制<!-- 关闭⼀对⼀ 延迟加载 -->
<resultMap id="orderMap" type="order">
<id column="id" property="id"></id>
<result column="ordertime" property="ordertime"></result>
<result column="total" property="total"></result>
<!--
fetchType="lazy" 懒加载策略
fetchType="eager" ⽴即加载策略
-->
<association property="user" column="uid" javaType="user"
select="com.zjq.dao.UserMapper.findById" fetchType="eager">
</association>
</resultMap>
<select id="findAll" resultMap="orderMap">
SELECT * from orders
</select>
延迟加载原理实现
它的原理是,使⽤ CGLIB 或 Javassist( 默认 ) 创建⽬标对象的代理对象。当调⽤代理对象的延迟加载属性的 getting ⽅法时,进⼊拦截器⽅法。⽐如调⽤ a.getB().getName() ⽅法,进⼊拦截器的invoke(…) ⽅法,发现 a.getB() 需要延迟加载时,那么就会单独发送事先保存好的查询关联 B对象的 SQL ,把 B 查询上来,然后调⽤a.setB(b) ⽅法,于是 a 对象 b 属性就有值了,接着完成a.getB().getName() ⽅法的调⽤。这就是延迟加载的基本原理 总结:延迟加载主要是通过动态代理的形式实现,通过代理拦截到指定⽅法,执⾏数据加载。
延迟加载原理(源码剖析)
MyBatis延迟加载主要使⽤:Javassist,Cglib实现,类图展示:
Setting 配置加载:
代码语言:javascript复制public class Configuration {
/** aggressiveLazyLoading:
* 当开启时,任何⽅法的调⽤都会加载该对象的所有属性。否则,每个属性会按需加载(参考
lazyLoadTriggerMethods).
* 默认为true
* */
protected boolean aggressiveLazyLoading;
/**
* 延迟加载触发⽅法
*/
protected Set<String> lazyLoadTriggerMethods = new HashSet<String>(Arrays.asList(new String[] { "equals", "clone", "hashCode", "toString" }));
/** 是否开启延迟加载 */
protected boolean lazyLoadingEnabled = false;
/**
* 默认使⽤Javassist代理⼯⼚
* @param proxyFactory
*/
public void setProxyFactory(ProxyFactory proxyFactory) {
if (proxyFactory == null) {
proxyFactory = new JavassistProxyFactory();
}
this.proxyFactory = proxyFactory;
}
//省略...
}
延迟加载代理对象创建
Mybatis的查询结果是由ResultSetHandler接⼝的handleResultSets()⽅法处理的。ResultSetHandler接⼝只有⼀个实现,DefaultResultSetHandler,接下来看下延迟加载相关的⼀个核⼼的⽅法。
代码语言:javascript复制 //创建结果对象
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<Class<?>>();
final List<Object> constructorArgs = new ArrayList<Object>();
//创建返回的结果映射的真实对象
Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix);
if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
for (ResultMapping propertyMapping : propertyMappings) {
// issue gcode #109 && issue #149
// 判断属性有没配置嵌套查询,如果有就创建代理对象
if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) {
//创建延迟加载代理对象
resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
break;
}
}
}
this.useConstructorMappings = resultObject != null && !constructorArgTypes.isEmpty(); // set current mapping result
return resultObject;
}
默认采⽤javassistProxy进⾏代理对象的创建。
代码语言:javascript复制public class Configuration {
protected ProxyFactory proxyFactory = new JavassistProxyFactory();
}
JavasisstProxyFactory实现
代码语言:javascript复制public class JavassistProxyFactory implements org.apache.ibatis.executor.loader.ProxyFactory {
/**
* 接⼝实现
* @param target ⽬标结果对象
* @param lazyLoader 延迟加载对象
* @param configuration 配置
* @param objectFactory 对象⼯⼚
* @param constructorArgTypes 构造参数类型
* @param constructorArgs 构造参数值
* @return
*/
@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);
}
/**
* 创建代理对象
* @param type
* @param callback
* @param constructorArgTypes
* @param constructorArgs
* @return
*/
static Object crateProxy(Class<?> type, MethodHandler callback, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
ProxyFactory enhancer = new ProxyFactory();
enhancer.setSuperclass(type);
try {
//通过获取对象⽅法,判断是否存在该⽅法
type.getDeclaredMethod(WRITE_REPLACE_METHOD);
// ObjectOutputStream will call writeReplace of objects returned by writeReplace
if (log.isDebugEnabled()) {
log.debug(WRITE_REPLACE_METHOD " method was found on bean " type ", make sure it returns this");
}
//没找到该⽅法,实现接⼝
} catch (NoSuchMethodException e) {
enhancer.setInterfaces(new Class[]{WriteReplaceInterface.class});
} catch (SecurityException e) {
// nothing to do here
}
Object enhanced;
Class<?>[] typesArray = constructorArgTypes.toArray(new Class[constructorArgTypes.size()]);
Object[] valuesArray = constructorArgs.toArray(new Object[constructorArgs.size()]);
try {
//创建新的代理对象
enhanced = enhancer.create(typesArray, valuesArray);
} catch (Exception e) {
throw new ExecutorException("Error creating lazy proxy. Cause: " e, e);
}
//设置代理执⾏器
((Proxy) enhanced).setHandler(callback);
return enhanced;
}
/**
* 内部类代理对象实现,核⼼逻辑执⾏
*/
private static class EnhancedResultObjectProxyImpl implements MethodHandler {
private final Class<?> type;
private final ResultLoaderMap lazyLoader;
private final boolean aggressive;
private final Set<String> lazyLoadTriggerMethods;
private final ObjectFactory objectFactory;
private final List<Class<?>> constructorArgTypes;
private final List<Object> constructorArgs;
private EnhancedResultObjectProxyImpl(Class<?> type, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
this.type = type;
this.lazyLoader = lazyLoader;
this.aggressive = configuration.isAggressiveLazyLoading();
this.lazyLoadTriggerMethods = configuration.getLazyLoadTriggerMethods();
this.objectFactory = objectFactory;
this.constructorArgTypes = constructorArgTypes;
this.constructorArgs = constructorArgs;
}
public static Object createProxy(Object target, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
final Class<?> type = target.getClass();
EnhancedResultObjectProxyImpl callback = new EnhancedResultObjectProxyImpl(type, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
//调用外部类的方法
Object enhanced = crateProxy(type, callback, constructorArgTypes, constructorArgs);
PropertyCopier.copyBeanProperties(type, target, enhanced);
return enhanced;
}
/**
* 代理对象执⾏
* @param enhanced 原对象
* @param method 原对象⽅法
* @param methodProxy 代理⽅法
* @param args ⽅法参数
* @return
* @throws Throwable
*/
@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;
//忽略暂未找到具体作⽤
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 {
//延迟加载数量⼤于0
if (lazyLoader.size() > 0 && !FINALIZE_METHOD.equals(methodName)) {
//aggressive ⼀次加载性所有需要要延迟加载属性或者包含触发延迟加载⽅法
if (aggressive || lazyLoadTriggerMethods.contains(methodName)) {
//⼀次全部加载
lazyLoader.loadAll();
} else if (PropertyNamer.isSetter(methodName)) {
//判断是否为set⽅法,set⽅法不需要延迟加载
final String property = PropertyNamer.methodToProperty(methodName);
lazyLoader.remove(property);
} else if (PropertyNamer.isGetter(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);
}
}
}
}
注意事项
**IDEA调试问题: **当配置aggressiveLazyLoading=true,在使⽤IDEA进⾏调试的时候,如果断点打到代理执⾏逻辑当中,你会发现延迟加载的代码永远都不能进⼊,总是会被提前执⾏。 主要产⽣的原因在aggressiveLazyLoading,因为在调试的时候,IDEA的Debuger窗体中已经触发了延迟加载对象的⽅法。