mybatis源码学习--mybatis懒加载内部原理
来源:互联网 发布:excel数据清洗方法 编辑:程序博客网 时间:2024/05/18 02:47
笔者最近研究mybatis比较多,所以打算把最近研究的mybatis的心得写出来,以免以后忘记,算是对这阶段的总结吧
环境:
mybatis-3.2.7
mybatis的懒加载配置什么的我就不详细说了,可以到我的github地址,看我的mybatis-demo <https://github.com/ht5678/mybatis-demo.git>,里边有详细的例子
我在这里画了一个图,简单的描述一下懒加载的流程,(画的不好。。。)
画的不好,,,可能大家看不懂,我来简单的给大家说一下流程,其实主要分两个逻辑:
启动懒加载
mybatis初始化返回类型的时候,会返回一个cglib代理对象,这样的话,该对象的关联对象(例如一对多,多对一)相关信息就会在loadpair里边,并且添加到loadmap中,cglib对象会过滤get,set ,is,"equals", "clone", "hashCode", "toString"触发方法,然后才会调用loadpair来加载关联对象的值,这部分的详细源码下面我会带大家看
不启动懒加载
不会返回代理对象,返回原生对象,然后会在一开始的时候就加载关联对象和sql中指定的所有属性
接下来,我会详细跟大家说一下mybatis是怎样实现懒加载的:
首先,懒加载会用到这两个配置
<!-- 查询时,关闭关联对象即时加载以提高性能 --><setting name="lazyLoadingEnabled" value="true" /><!-- 设置关联对象加载的形态,此处为按需加载字段(加载字段由SQL指 定),不会加载关联表的所有字段,以提高性能 --><setting name="aggressiveLazyLoading" value="false" />
这两个配置,第一个毫无疑问,就是启动懒加载的;第二个呢,是mybatis执行完sql语句,组装返回的对象的时候,按sql指定的字段来加载,详细点说,就是如果没有调用指定的字段或者对象的get,set ,is,"equals", "clone", "hashCode", "toString"方法,该属性或者关联对象就不会在返回的对象结果中
使用懒加载的sql:
/** * 根据用户id查询用户角色 * @param userId * @return */@Select("select * from role_main a INNER JOIN user_role b ON a.id = b.role_id WHERE b.user_id=#{userId}")public List<RolePO> getRolesByUserId(@Param("userId")Integer userId);/** * 根据用户id查询用户角色名 * @param userId * @return */@Select("select a.role_name from role_main a INNER JOIN user_role b ON a.id = b.role_id WHERE b.user_id=#{userId}")public Set<String> getRoleNamesByUserId(@Param("userId")Integer userId);/** * 根据userid查询用户的所有权限 * @param userId * @return */@Select("SELECT a.permission_name FROM permission_main a INNER JOIN role_permission b ON a.id=b.permission_id WHERE b.role_id IN (SELECT d.role_id from user_main c INNER JOIN user_role d ON c.id = d.user_id WHERE c.id=#{userId})")public Set<String> getPermissionsByUserId(@Param("userId")Integer userId);/** * 通过用户名查询用户信息 * @param username * @return */@Select("select * from user_main where username=#{username}")@Results({ @Result(property = "roleNames", column = "id", many = @Many(fetchType=FetchType.LAZY,select = "getRoleNamesByUserId")), @Result(property = "permissionNames", column = "id", many = @Many(fetchType=FetchType.LAZY,select = "getPermissionsByUserId")) })public UserPO getUserByUsername(@Param("username")String username);
方法getUserByUserName就是使用了懒加载的查询,根据上面所描述的,以roleNames和permissionNames为例,在这里,它们俩就是所说的关联对象,如果没有调用它们的get,set ,is,"equals", "clone", "hashCode", "toString"方法,这两个关联的集合对象是不会有值的,只有触发了方法,才会从数据库中查询值
接下来,我带大家看看,mybatis在使用懒加载的时候做了什么,然后又在触发懒加载的时候做了什么
mybatis在使用懒加载的时候,是怎样保留懒加载的信息的?
这里额外的补充一下,在mybatis中,使用了很多动态代理,非常的普遍,使用最多的是jdk proxy,然后就是懒加载用到的cglib(默认)和javassist,关于懒加载这部分,笔者打算只是讲述cglib实现的懒加载,javassist是和cglib差不多的,只是技术不同,这两种懒加载笔者也就不说谁好谁坏了
回到正题,mybatis在懒加载的时候是怎么保留关联对象查询数据库的信息的:
以mybatis执行getUserByUserName的查询为例,在初始化UserPO赋值从数据库中查询的value的时候,因为启用了懒加载,所以USerPO的引用对象会是一个cglib代理对象,代理对象的初始化部分如下CglibProxyFactory,EnhancedResultObjectProxyImpl:
/** * @author Clinton Begin */public class CglibProxyFactory implements ProxyFactory { private static final Log log = LogFactory.getLog(CglibProxyFactory.class); private static final String FINALIZE_METHOD = "finalize"; private static final String WRITE_REPLACE_METHOD = "writeReplace"; /** * 无参构造函数,初始化CglibProxyFactory * 加载net.sf.cglib.proxy.Enhancer(cglib) * */ public CglibProxyFactory() { try { Resources.classForName("net.sf.cglib.proxy.Enhancer"); } catch (Throwable e) { throw new IllegalStateException("Cannot enable lazy loading because CGLIB is not available. Add CGLIB to your classpath.", e); } } /** * 使用内部类EnhancedResultObjectProxyImpl构建target类的代理 */ 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); } public Object createDeserializationProxy(Object target, Map<String, ResultLoaderMap.LoadPair> unloadedProperties, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) { return EnhancedDeserializationProxyImpl.createProxy(target, unloadedProperties, objectFactory, constructorArgTypes, constructorArgs); } public void setProperties(Properties properties) { } /** * 使用cglib创建代理对象 * @param type 原生的对象类型 * @param callback 回调方法,就是实现了MethodInterceptor接口的子类,会有accept方法 * @param constructorArgTypes 构造函数的参数类型 * @param constructorArgs 构造函数的参数值 * @return */ private static Object crateProxy(Class<?> type, Callback callback, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) { Enhancer enhancer = new Enhancer(); //回调方法,就是实现了MethodInterceptor接口的子类,会有accept方法 enhancer.setCallback(callback); //要生成代理对象的原生类 enhancer.setSuperclass(type); try { //获取目标类型的 writeReplace 方法,如果没有,异常中代理类设置enhancer.setInterfaces(new Class[]{WriteReplaceInterface.class}); type.getDeclaredMethod(WRITE_REPLACE_METHOD); // ObjectOutputStream will call writeReplace of objects returned by writeReplace 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 = null; //如果构造函数没有参数,创建代理对象 if (constructorArgTypes.isEmpty()) { enhanced = enhancer.create(); } else { //否则,初始化带有参数的构造函数 Class<?>[] typesArray = constructorArgTypes.toArray(new Class[constructorArgTypes.size()]); Object[] valuesArray = constructorArgs.toArray(new Object[constructorArgs.size()]); //创建带有参数的代理对象 enhanced = enhancer.create(typesArray, valuesArray); } return enhanced; } /** * * 类CglibProxyFactory.java的实现描述:cglib的callable回调的类 * @author yuezhihua 2015年5月19日 下午6:26:05 */ private static class EnhancedResultObjectProxyImpl implements MethodInterceptor {//要代理的对象类型 private Class<?> type; private ResultLoaderMap lazyLoader; private boolean aggressive; private Set<String> lazyLoadTriggerMethods; private ObjectFactory objectFactory; private List<Class<?>> constructorArgTypes; private List<Object> constructorArgs; /** * 构造函数 * @param type 要代理的对象类型 * @param lazyLoader TODO: * @param configuration 配置 * @param objectFactory objectFactory用来初始化对象 * @param constructorArgTypes type的有参构造函数的参数类型和参数名 * @param constructorArgs type的有参构造函数的参数类型和参数名 */ private EnhancedResultObjectProxyImpl(Class<?> type, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) { //要代理的对象类型 this.type = type; this.lazyLoader = lazyLoader; //是否懒加载aggressive,默认为true this.aggressive = configuration.isAggressiveLazyLoading(); //懒加载触发方法 "equals", "clone", "hashCode", "toString" this.lazyLoadTriggerMethods = configuration.getLazyLoadTriggerMethods(); //objectFactory用来初始化对象 this.objectFactory = objectFactory; //type的有参构造函数的参数类型和参数名 this.constructorArgTypes = constructorArgTypes; this.constructorArgs = constructorArgs; } /** * 构建target类的代理 * @param target 要构建代理的对象 * @param lazyLoader 在DefaultResultSetHandler的getRowValue方法初始化 * @param configuration 配置类 * @param objectFactory class实例化为object的工场 * @param constructorArgTypes 构造函数的参数类型 * @param constructorArgs 构造函数的参数值 * @return */ public static Object createProxy(Object target, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) { final Class<?> type = target.getClass(); //初始化一个EnhancedResultObjectProxyImpl内部类 EnhancedResultObjectProxyImpl callback = new EnhancedResultObjectProxyImpl(type, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs); //使用cglib创建代理对象 Object enhanced = crateProxy(type, callback, constructorArgTypes, constructorArgs); //target是type的初始化对象 //enhanced可能是type的代理对象 //将target对象的值赋值到enhanced,包括父类的字段赋值 PropertyCopier.copyBeanProperties(type, target, enhanced); //返回代理enhanced return enhanced; } /** * cglib拦截器实现 */ public Object intercept(Object enhanced, Method method, Object[] args, MethodProxy methodProxy) 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); } // 将sourceBean对象的值赋值到destinationBean,包括父类的字段赋值 PropertyCopier.copyBeanProperties(type, enhanced, original); //lazyLoader.size()保存需要延迟加载属性列表的个数 if (lazyLoader.size() > 0) { return new CglibSerialStateHolder(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 就会触发ResultLoader的loadResult方法完成数据的加载实现。 lazyLoader.loadAll(); } else if (PropertyNamer.isProperty(methodName)) { //将get或set方法名获取property字段名 final String property = PropertyNamer.methodToProperty(methodName); if (lazyLoader.hasLoader(property)) { lazyLoader.load(property); } } } } } return methodProxy.invokeSuper(enhanced, args); } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } } } private static class EnhancedDeserializationProxyImpl extends AbstractEnhancedDeserializationProxy implements MethodInterceptor { private EnhancedDeserializationProxyImpl(Class<?> type, Map<String, ResultLoaderMap.LoadPair> unloadedProperties, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) { super(type, unloadedProperties, objectFactory, constructorArgTypes, constructorArgs); } public static Object createProxy(Object target, Map<String, ResultLoaderMap.LoadPair> unloadedProperties, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) { final Class<?> type = target.getClass(); EnhancedDeserializationProxyImpl callback = new EnhancedDeserializationProxyImpl(type, unloadedProperties, objectFactory, constructorArgTypes, constructorArgs); Object enhanced = crateProxy(type, callback, constructorArgTypes, constructorArgs); PropertyCopier.copyBeanProperties(type, target, enhanced); return enhanced; } @Override public Object intercept(Object enhanced, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { final Object o = super.invoke(enhanced, method, args); return (o instanceof AbstractSerialStateHolder) ? o : methodProxy.invokeSuper(o, args); } @Override protected AbstractSerialStateHolder newSerialStateHolder(Object userBean, Map<String, ResultLoaderMap.LoadPair> unloadedProperties, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) { return new CglibSerialStateHolder(userBean, unloadedProperties, objectFactory, constructorArgTypes, constructorArgs); } }}
上面是从mybatis中粘出来的源码,大家注意带有注释的crateProxy方法和EnhancedResultObjectProxyImpl中的intercept方法,这两个方法比较重要,crateProxy是初始化返回对象类型的代理对象,这里会默认给代理对象增加一个WriteReplaceInterface接口,这个接口也是用来做懒加载触发的,笔者不是很清楚,不做说明;其实上面说了这么多次的懒加载触发,什么的,实际点说,就是intercept方法,这里我要说一下,代理对象的每个方法调用都会经过intercept方法,大家看intercept方法中的判断lazyLoadTriggerMethods,这个lazyLoadTriggerMethods就是上面一直说的懒加载的默认触发方法,"equals", "clone", "hashCode", "toString",因为这个默认触发方法,笔者还吃了个亏,详细请看,mybatis注解方式懒加载失效分析,这几个默认方法是在configuration中配置的,大家可以根据需要在配置文件中自定义触发方法;除了这几个默认的触发方法,还有PropertyNamer.isProperty(methodName)这段代码,他会判断当前方法是不是代理对象的get,is,set方法,如果是的话,也会触发懒加载来查询数据库。
但是到这里懒加载保存信息部分还是没有完,我们来看,mybatis组装返回对象的时候,是怎么保留懒加载信息的,懒加载信息又是什么:
这里就要看DefaultResultSetHandler了,因为这个类比较多,所以笔者就不贴源码了大家可以对照着源码来看一下这部分,我先给大家看一下这个类中处理查询返回结果的方法时序图:
这个是DefaultResultSetHandler处理从数据库查询的数据的处理流程图,其中懒加载是在getNestedQueryMappingValue方法中的
//初始化resultloader final ResultLoader resultLoader = new ResultLoader(configuration, executor, nestedQuery, nestedQueryParameterObject, targetType, key, nestedBoundSql); //如果启用了懒加载,初始化一个loadpair到loadermap里边,如果没有启用,直接获取value并且返回 if (propertyMapping.isLazy()) { lazyLoader.addLoader(property, metaResultObject, resultLoader); } else {//直接加载 value = resultLoader.loadResult(); }
这一段,mybatis会将关联对象,就是上面提到的roleNames和permissionNames两个一对多的嵌套查询会以loadpair对象的方式加到ResultLoaderMap.loadmap中,
其中loadpair记录了关联对象mybatis查询要用到的信息,metaResultObject(返回对象的metaobject),ResultLoader(包含了参数,返回映射,缓存key,配置等),executor(sql执行器)。。。
然后包含这些信息的loadpair就会放到loadmap中了,这部分完成解说了,接下来,就要看mybatis懒加载触发的时候是怎么使用loadpair来查询数据库的。
mybatis触发懒加载使用loadpair查询数据库并且返回组装对象:
说到触发的话,就要回到CglibProxyFactory.EnhancedResultObjectProxyImpl的intercept方法上了,懒加载方法被触发以后会调用lazyLoader.load(property);方法,
这个方法会先从loadmap中将loadpair移除,然后调用loadpair的load方法,
/** * 执行懒加载查询,获取数据并且set到userObject中返回 * @param userObject * @throws SQLException */ public void load(final Object userObject) throws SQLException { //合法性校验 if (this.metaResultObject == null || this.resultLoader == null) { if (this.mappedParameter == null) { throw new ExecutorException("Property [" + this.property + "] cannot be loaded because " + "required parameter of mapped statement [" + this.mappedStatement + "] is not serializable."); } //获取mappedstatement并且校验 final Configuration config = this.getConfiguration(); final MappedStatement ms = config.getMappedStatement(this.mappedStatement); if (ms == null) { throw new ExecutorException("Cannot lazy load property [" + this.property + "] of deserialized object [" + userObject.getClass() + "] because configuration does not contain statement [" + this.mappedStatement + "]"); } //使用userObject构建metaobject,并且重新构建resultloader对象 this.metaResultObject = config.newMetaObject(userObject); this.resultLoader = new ResultLoader(config, new ClosedExecutor(), ms, this.mappedParameter, metaResultObject.getSetterType(this.property), null, null); } /* We are using a new executor because we may be (and likely are) on a new thread * and executors aren't thread safe. (Is this sufficient?) * * A better approach would be making executors thread safe. */ if (this.serializationCheck == null) { final ResultLoader old = this.resultLoader; this.resultLoader = new ResultLoader(old.configuration, new ClosedExecutor(), old.mappedStatement, old.parameterObject, old.targetType, old.cacheKey, old.boundSql); } //获取数据库查询结果并且set到结果对象返回 this.metaResultObject.setValue(property, this.resultLoader.loadResult()); }
方法中会将this.resultLoader.loadResult()的值赋给返回对象的property字段,loadResult方法中会使用executor来执行查询获取结果然后组装成返回对象返回
现在懒加载部分的原理已经说完了,当然,里边有很多没有详细讲解的部分,也有很多大家还不详细理解的部分,比如executor,其他的查询结构,mappedstatement,metaobject...等等,笔者打算做一个专题,尽量将自己领悟的mybatis组件原理呈现给大家,,,希望大家能够支持
- mybatis源码学习--mybatis懒加载内部原理
- mybatis 的懒加载原理
- mybatis 的懒加载原理
- mybatis 的懒加载原理
- Mybatis原理源码分析
- mybatis association 懒加载实现原理
- Mybatis原理与学习
- Mybatis源码学习
- MyBatis开始学习源码
- mybatis源码DEBUG学习
- mybatis源码学习
- MyBatis插件原理-源码解读
- MyBatis学习四 懒加载和缓存
- 【MyBatis学习11】MyBatis中的延迟加载
- 【MyBatis学习11】MyBatis中的延迟加载
- 【MyBatis学习11】MyBatis中的延迟加载
- Mybatis学习(10)-MyBatis中的延迟加载
- 【MyBatis学习11】MyBatis中的延迟加载
- IOS 学习笔记
- Java字节码中invokespecial与invokevirtual指令的解析
- 设计模式一览
- (转)Android 操作系统的内存回收机制
- iOS 使用纯代码自定义UITableViewCell实现一个简单的微博界面布局
- mybatis源码学习--mybatis懒加载内部原理
- G --Task schedule(HDU4907
- Snail—1-9这9个数字划分成三个3位数,第一个分别是第二、三个的2倍,3倍
- POJ1928 The Peanuts
- c、c++引用
- POJ 3007 Organize Your Train part II(枚举)
- 多重引导系统启动流程
- 编译Wireshark 1.12.6
- html中读取xml文件中中文出现乱码