Mybatis源码剖析:延迟加载源码剖析

Posted 丿涛哥哥

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Mybatis源码剖析:延迟加载源码剖析相关的知识,希望对你有一定的参考价值。

Mybatis源码剖析:延迟加载源码剖析

1、什么是延迟加载?

在开发过程中很多时候我们并不需要总是在加载用户信息时就一定要加载他的订单信息。此时就是我们所说的延迟加载。

举个栗子

* 在一对多中,当我们有一个用户,它有个100个订单
	在查询用户的时候,要不要把关联的订单查出来?
	在查询订单的时候,要不要把关联的用户查出来?
	
* 回答
	在查询用户时,用户下的订单应该是,什么时候用,什么时候查询。
	在查询订单时,订单所属的用户信息应该是随着订单一起查询出来。

延迟加载

延迟加载也称懒加载。

* 优点:
	先从单表查询,需要时再从关联表去关联查询,大大提高数据库性能,因为查询单表要比关联查询多张表
	速度要快。
	
* 缺点:
	因为只有当需要用到数据时,才会进行数据库查询,这样在大批量数据查询时,因为查询工作也要消耗时
	间,所以可能造成用户等待时间变长,造成用户体验下降。
	
* 在多表中:
	一对多,多对多:通常情况下采用延迟加载
	一对一(多对一):通常情况下采用立即加载
	
* 注意:
	延迟加载是基于嵌套查询来实现的

2、 实现

局部延迟加载

修改association和collection标签中的fetchType属性,修改局部的加载策略。

<!-- 开启一对多 延迟加载 -->
<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.tao.dao.OrderMapper.findByUid" fetchType="lazy">
    </collection>
</resultMap>
<select id="findAll" resultMap="userMap">
	SELECT * FROM user
</select>

全局延迟加载

在Mybatis的核心配置文件中可以使用setting标签修改全局的加载策略。

<settings>
	<!--开启全局延迟加载功能-->
	<setting name="lazyLoadingEnabled" value="true"/>
</settings>

注意

<!-- 关闭一对一 延迟加载 -->
<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.tao.dao.UserMapper.findById" fetchType="eager">
    </association>
</resultMap>
<select id="findAll" resultMap="orderMap">
	SELECT * from orders
</select>

3、 延迟加载原理实现

使用 CGLIB 或 Javassist( 默认 ) 创建目标对象的代理对象。当调用代理对象的延迟加载属性的 getting 方法时,进入拦截器方法。比如调用 a.getB().getName() 方法,进入拦截器的 invoke(…) 方法,发现 a.getB() 需要延迟加载时,那么就会单独发送事先保存好的查询关联 B 对象的 SQL ,把 B 查询上来,然后调用 a.setB(b) 方法,于是 a 对象 b 属性就有值了,接着完成 a.getB().getName() 方法的调用。这就是延迟加载的基本原理。

总结:延迟加载主要是通过动态代理的形式实现,通过代理拦截到指定方法,执行数据加载。

4、延迟加载原理(源码剖析)

MyBatis延迟加载主要使用:Javassist,Cglib实现

Setting 配置加载:

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,接下来看下延迟加载相关的一个核心的方法。

//#mark 创建结果对象
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>();
	//#mark 创建返回的结果映射的真实对象
	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) {
			// 判断属性有没配置嵌套查询,如果有就创建代理对象
			if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) {
				//#mark 创建延迟加载代理对象
				resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader,
								configuration, objectFactory, constructorArgTypes, constructorArgs);
				break;
			}
		}
	}
	this.useConstructorMappings = resultObject != null && !constructorArgTypes.isEmpty(); // set current mapping result
	return resultObject;
}

默认采用javassistProxy进行代理对象的创建

protected ProxyFactort proxyFactory = new JavassistProxyFactory();

JavasisstProxyFactory实现

public class JavassistProxyFactory implements org.apache.ibatis.executor.loader.ProxyFactory {
    /**
    * 接口实现
    * target 目标结果对象
    * lazyLoader 延迟加载对象
    * configuration 配置
    * objectFactory 对象工厂
    * constructorArgTypes 构造参数类型
    * 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);
	}
    
    //省略部分源码...
    
    /**
    * 代理对象实现,核心逻辑执行
    */
	private static class EnhancedResultObjectProxyImpl implements MethodHandler {
    /**
    * 创建代理对象
    */
	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);
			
			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;
	}
        
    /**
    * 代理对象执行
    *  enhanced 原对象
    *  method 原对象方法
    *  methodProxy 代理方法
    *  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() &gt; 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)) {
							log.debug("==> laze lod trigger method:" +
									methodName + ",proxy method:" + methodProxy.getName() + "class:" + enhanced.getClass());
                            //一次全部加载
                            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);
								log.debug("load one :" + methodName);
							}
						}
					}
				}
			}
			return methodProxy.invoke(enhanced, args);
		} catch (Throwable t) {
			throw ExceptionUtil.unwrapThrowable(t);
		}
	}
}

注意事项

IDEA调试问题 当配置aggressiveLazyLoading=true,在使用IDEA进行调试的时候,如果断点打到 代理执行逻辑当中,你会发现延迟加载的代码永远都不能进入,总是会被提前执行。 主要产生的原 因在aggressiveLazyLoading,因为在调试的时候,IDEA的Debuger窗体中已经触发了延迟加载对象的方法。

以上是关于Mybatis源码剖析:延迟加载源码剖析的主要内容,如果未能解决你的问题,请参考以下文章

Spring IOC源码剖析:lazy-init 延迟加载机制和循环依赖问题

Mybatis源码剖析:传统开发方式源码剖析

Mybatis源码剖析-黑马程序员

Mybatis源码剖析:二级缓存源码剖析

Mybatis源码剖析:使用了工厂模式构建者模式动态代理模式等等知识实现对Mybatis的自定义

mybatis源码级别深度剖析