HuKai's Blog HuKai's Blog
首页
  • Java核心技术

    • Java基础
    • Java并发编程
    • JVM
    • Java新特性
  • Spring生态

    • Spring5
    • SpringMVC
    • SpringBoot
  • 开源框架

    • MyBatis
  • 计算机网络
  • 操作系统
  • 数据结构与算法
  • 设计模式
  • SQL数据库

    • MySQL
    • Oracle
  • NoSQL数据库

    • Redis
    • MongoDB
  • 页面样式

    • HTML
    • CSS
  • JavaScript

    • JavaScript基础
    • ECMAScript6教程
    • TypeScript
  • 前端框架

    • Vue
    • Webpack
  • NIO
  • Netty
  • RabbitMQ
  • 技术文档

    • GitHub技巧
    • 博客搭建
    • 技术笔记
  • 优质文章

    • 小技巧
    • 解决方案
GitHub (opens new window)

HuKai

梦想成为全栈的保安
首页
  • Java核心技术

    • Java基础
    • Java并发编程
    • JVM
    • Java新特性
  • Spring生态

    • Spring5
    • SpringMVC
    • SpringBoot
  • 开源框架

    • MyBatis
  • 计算机网络
  • 操作系统
  • 数据结构与算法
  • 设计模式
  • SQL数据库

    • MySQL
    • Oracle
  • NoSQL数据库

    • Redis
    • MongoDB
  • 页面样式

    • HTML
    • CSS
  • JavaScript

    • JavaScript基础
    • ECMAScript6教程
    • TypeScript
  • 前端框架

    • Vue
    • Webpack
  • NIO
  • Netty
  • RabbitMQ
  • 技术文档

    • GitHub技巧
    • 博客搭建
    • 技术笔记
  • 优质文章

    • 小技巧
    • 解决方案
GitHub (opens new window)
  • Java基础

  • Java并发编程

  • JVM

  • Java新特性

  • Spring5

  • SpringMVC

  • SpringBoot

  • MyBatis

    • 应用篇

    • 源码篇

      • MyBatis源码剖析-数据处理
      • MyBatis源码剖析-插件机制
      • MyBatis源码剖析-二级缓存
      • MyBatis源码剖析-延迟加载
        • 1. 延迟加载相关概念
        • 2. 延迟加载源码剖析
        • 3. 总结
    • 扩展篇

  • Java
  • MyBatis
  • 源码篇
HuKai
2022-03-20
目录

MyBatis源码剖析-延迟加载

# 1. 延迟加载相关概念

# 1.1 什么是延迟加载?

举个例子:如果查询员工信息并且关联查询员工所属部门信息。如果先查询员工单信息即可满足要求,当我们需要查询部门信息时再查询用部门信息。把对部门信息的按需去查询就是延迟加载。

所以延迟加载即先从单表查询、需要时再从关联表去关联查询,大大提高数据库性能,因为查询单表要比关联查询多张表速度要快。

我们来对比一下:

-- 关联查询
SELECT e.*,d.* FROM dept d inner join emp e on d.DEPTNO = e.DEPTNO;

-- 单表分次查询
select * from emp;
select * from dept where deptno = '查询出员工的部门编号';
1
2
3
4
5
6

这就比较直观了,也就是说,我把关联查询分两次来做,而不是一次性查出所有的。第一步只查询单表emp,必然会查出emp中的一个deptno字段,然后我再根据这个deptno查dept表,也是单表查询。下面来总结一下如何使用这个延迟加载。

# 1.2 实现延迟加载

前面博文中总结了resultMap可以实现高级映射(使用association、collection实现一对一及一对多映射),其实association和collection还具备延迟加载的功能,这里我就拿association来说明,collection和association使用的方法都是一样的。

在association和collection标签中都有一个fetchType属性,通过修改它的值,可以修改局部的加载策略:

<resultMap id="empDeptMap" type="emp">
    <result column="empno" property="empno"></result>
    <result column="ename" property="ename"></result>
    <result column="job" property="job"></result>
    <result column="mgr" property="mgr"></result>
    <result column="hiredate" property="hiredate"></result>
    <result column="sal" property="sal"></result>
    <result column="comm" property="comm"></result>
    <result column="deptno" property="deptno"></result>
    <!--
        fetchType="lazy" 懒加载策略
        fetchType="eager" 立即加载策略
    -->
    <association property="dept" column="deptno" javaType="dept"
                 select="com.hukai.demo.dao.IDeptDao.findById" fetchType="lazy"></association>
</resultMap>

<select id="findEmpAndDept" resultMap="empDeptMap">
    select * from emp
</select>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

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

<settings>
    <!-- 打开延迟加载的开关 -->
    <setting name="lazyLoadingEnabled" value="true"/>
    <!-- 将积极加载改为消极加载,即延迟加载 -->
    <setting name="aggressiveLazyLoading" value="false"/>
</settings>
1
2
3
4
5
6

配置完成后我们先测试没有调用延迟加载属性时的sql发送情况:

    @Test
    public void testFindEmpAndDeptById() throws Exception{
        List<Emp> empAndDept = empDao.findEmpAndDept();
        empAndDept.forEach(emp -> System.out.println(emp.getEname()));
//        empAndDept.forEach(emp -> System.out.println(emp.getDept()));
    }
1
2
3
4
5
6

效果很明显,只执行了一条sql,查询了所有员工信息,并没有查询部门信息。

接下来我们测试调用了延迟加载属性时的sql发送情况:

@Test
public void testFindEmpAndDeptById() throws Exception{
    List<Emp> empAndDept = empDao.findEmpAndDept();
//        empAndDept.forEach(emp -> System.out.println(emp.getEname()));
    empAndDept.forEach(emp -> System.out.println(emp.getDept()));
}
1
2
3
4
5
6

使用了延迟加载,将关联查询分成了两次单表查询。

# 1.3 延迟加载原理实现

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

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

# 2. 延迟加载源码剖析

# 2.1 延迟加载相关配置解析

在settings标签中,有4个和延迟加载相关属性:

设置名 描述 有效值 默认值
lazyLoadingEnabled 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。 true | false false
aggressiveLazyLoading 开启时,任一方法的调用都会加载该对象的所有延迟加载属性。 否则,每个延迟加载属性会按需加载。 true | false false
lazyLoadTriggerMethods 指定对象的哪些方法触发一次延迟加载。 用逗号分隔的方法列表。 equals,clone,hashCode,toString
proxyFactory 指定 Mybatis 创建可延迟加载对象所用到的代理工具。 CGLIB | JAVASSIST JAVASSIST

Setting 配置加载:

public class Configuration {
    /**
     * 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置fetchType属性来覆盖该项的开关状态。
     */
    protected boolean lazyLoadingEnabled = false;
    protected ProxyFactory proxyFactory = new JavassistProxyFactory();
    /**
     * 当开启时,任何方法的调用都会加载该对象的所有属性。否则,每个属性会按需加载(参考lazyLoadTriggerMethods)
     */
    protected boolean aggressiveLazyLoading;
    /**
     * 指定哪个对象的方法触发一次延迟加载。
     */
    protected Set<String> lazyLoadTriggerMethods = new HashSet<>(Arrays.asList("equals", "clone", "hashCode", "toString"));
    
    public void setProxyFactory(ProxyFactory proxyFactory) {
        if (proxyFactory == null) {
            proxyFactory = new JavassistProxyFactory();
        }
        this.proxyFactory = proxyFactory;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

有了这些属性值之后,我们来看看延迟加载代理对象是如何创建的。

# 2.2 延迟加载代理对象创建

Mybatis的查询结果是由ResultSetHandler接口的handleResultSets()方法处理的。ResultSetHandler接口只有一个实现:DefaultResultSetHandler。接下来看下延迟加载相关的一个核心的方法:

// 创建映射后的结果对象
private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
    // useConstructorMappings ,表示是否使用构造方法创建该结果对象。此处将其重置
    this.useConstructorMappings = false; // reset previous mapping result
    final List<Class<?>> constructorArgTypes = new ArrayList<>(); // 记录使用的构造方法的参数类型的数组
    final List<Object> constructorArgs = new ArrayList<>(); // 记录使用的构造方法的参数值的数组
    // 创建映射后的结果对象
    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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

默认采用javassistProxy进行代理对象的创建,我们转到JavasisstProxyFactory类中看看代理类是如何创建的:

public class JavassistProxyFactory implements org.apache.ibatis.executor.loader.ProxyFactory {
        @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 {
        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 对象
            EnhancedResultObjectProxyImpl callback = new EnhancedResultObjectProxyImpl(type, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
            // 创建代理对象
            Object enhanced = crateProxy(type, callback, constructorArgTypes, constructorArgs);
            // 将 target 的属性,复制到 enhanced 中
            PropertyCopier.copyBeanProperties(type, target, enhanced);
            return enhanced;
        }

        @Override
        public Object invoke(Object enhanced, Method method, Method methodProxy, Object[] args) throws Throwable {
            final String methodName = method.getName();
            try {
                synchronized (lazyLoader) {
                    // 忽略 WRITE_REPLACE_METHOD ,和序列化相关
                    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 {
                        if (lazyLoader.size() > 0 && !FINALIZE_METHOD.equals(methodName)) {
                            // 加载所有延迟加载的属性
                            if (aggressive || lazyLoadTriggerMethods.contains(methodName)) {
                                lazyLoader.loadAll();
                            // 如果调用了 setting 方法,则不在使用延迟加载
                            } else if (PropertyNamer.isSetter(methodName)) {
                                final String property = PropertyNamer.methodToProperty(methodName);
                                lazyLoader.remove(property); // 移除
                            // 如果调用了 getting 方法,则执行延迟加载
                            } 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);
            }
        }
    }
    // 创建代理对象
    static Object crateProxy(Class<?> type, MethodHandler callback, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
        // 创建 javassist ProxyFactory 对象
        ProxyFactory enhancer = new ProxyFactory();
        // 设置父类
        enhancer.setSuperclass(type);

        // 根据情况,设置接口为 WriteReplaceInterface 。和序列化相关,可以无视
        try {
            type.getDeclaredMethod(WRITE_REPLACE_METHOD); // 如果已经存在 writeReplace 方法,则不用设置接口为 WriteReplaceInterface
            // 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}); // 如果不存在 writeReplace 方法,则设置接口为 WriteReplaceInterface
        } 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;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116

# 3. 总结

优点:

  • 先从单表查询,需要时再从关联表去关联查询,大大提高数据库性能,因为查询单表要比关联查询多张表速度要快。

缺点:

  • 因为只有当需要用到数据时,才会进行数据库查询,这样在大批量数据查询时,因为查询工作也要消耗时间,所以可能造成用户等待时间变长,造成用户体验下降。

因此,在多表中一对多,多对多通常情况下采用延迟加载,一对一(多对一)通常情况下采用立即加载。

编辑 (opens new window)
#MyBatis#源码剖析
上次更新: 2022/03/20, 16:39:15
MyBatis源码剖析-二级缓存
MyBatisPlus

← MyBatis源码剖析-二级缓存 MyBatisPlus→

最近更新
01
MyBatisPlus
03-20
02
MyBatis源码剖析-二级缓存
03-20
03
MyBatis源码剖析-插件机制
03-20
更多文章>
Theme by Vdoing | Copyright © 2021-2022 HuKai | 赣ICP备17016768号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式