首先要了解延迟加载的概念,知道单例模式的都知道延迟加载和立即加载

  • 延迟加载:只有在使用的时候才会创建并加载
  • 立即加载:在初始化的时候就创建,使用的时候直接拿去用

在我们数据库的查询中,也需要有这种策略,在一对多或者多对多的关系中,我们有时候可能并不需要另一方的信息,这个时候就需要使用延迟加载,延迟加载的完成都是使用嵌套查询实现的,什么是嵌套查询,下面进行展示:

  1. // 使用查询用户的时候查询订单来展示嵌套查询
  2. @Test
  3. public void testNestedQuery() {
  4. User user = userMapper.selectByIdForNestedQuery(1);
  5. System.out.println(user);
  6. }
  7. // 这里只能使用 resultMap,否则无法实现嵌套查询
  8. <resultMap id="nestedQuery" type="com.wangzhi.pojo.User">
  9. <result column="id" property="id" javaType="int"/>
  10. <result column="name" property="name" javaType="string"/>
  11. <!-- 这里是关键,使用select属性 -->
  12. <collection property="ordersList" ofType="com.wangzhi.pojo.Orders"
  13. select="com.wangzhi.mapper.OrdersMapper.selectByUserId" column="id">
  14. <result column="id" property="id" javaType="int"/>
  15. <result column="name" property="name" javaType="string"/>
  16. </collection>
  17. </resultMap>
  18. <select id="selectByIdForNestedQuery" resultMap="nestedQuery" parameterType="int">
  19. SELECT id, name FROM user WHERE id = #{id}
  20. </select>
  21. @Select("SELECT * FROM orders WHERE user_id = #{userId} ")
  22. List<Orders> selectByUserId(Integer userId);

测试的时候可以看到执行了两次sql,那就说明默认当前是立即加载。

实现延迟加载

局部延迟加载

局部延迟加载是针对于MappedStatement,其实就是一个属性fetchType,展示如下:

  1. <collection property="ordersList" ofType="com.wangzhi.pojo.Orders" select="com.wangzhi.mapper.OrdersMapper.selectByUserId" column="id" fetchType="lazy">
  2. <result column="id" property="id" javaType="int"/>
  3. <result column="name" property="name" javaType="string"/>
  4. </collection>

接下来在测试的时候输出对象可以先输出user.getName。可以发现查询的过程 一条sql语句。只有在用到ordersList的时候才会查询,输出另一条sql语句

全局延迟加载

全局加载就需要在配置文件中进行setting设置了。

  1. <settings>
  2. <setting name="lazyLoadingEnabled" value="true"/>
  3. </settings>

优先级

针对于延迟加载来说,局部延迟加载优先于全局延迟加载,这样我们就可以再开启全局延迟加载的时候,针对某些一对一的查询开启立即加载。

延迟加载的原理

原理其实还是动态代理,当调用代理对象的延迟加载属性的getting方法的时候,也就是前面的user.getOrdersList,这里user其实已经是代理对象了,就会进入拦截器invoke方法,发现当前属性需要延迟加载时,就会发送事先保存的sql进行查询。所以说延迟加载的本质就是通过代理的形式 的,通过代理拦截到指定方法,进行数据加载。

源码分析

Mybatis的查询结果是由ResultSetHandler接⼝的handleResultSets()⽅法处理的,所以我们需要从ResultSetHandler来入手进行查看,看到实现类只有一个 DefaultResultSetHandler。

  1. // 调用的一个方法顺序为: handleResultSets -> handleResultSet -> handleRowValues ->
  2. // handleRowValuesForNestedResultMap -> getRowValue -> createResultObject
  3. private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
  4. this.useConstructorMappings = false; // reset previous mapping result
  5. final List<Class<?>> constructorArgTypes = new ArrayList<>();
  6. final List<Object> constructorArgs = new ArrayList<>();
  7. Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix);
  8. if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
  9. final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
  10. for (ResultMapping propertyMapping : propertyMappings) {
  11. // 这里是重点,判断属性是否有延迟加载
  12. if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) {
  13. resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
  14. break;
  15. }
  16. }
  17. }
  18. this.useConstructorMappings = resultObject != null && !constructorArgTypes.isEmpty(); // set current mapping result
  19. return resultObject;
  20. }
  21. // 默认为JavassistProxyFactory,configuration的getProxyFactory()
  22. protected ProxyFactory proxyFactory = new JavassistProxyFactory();
  23. public ProxyFactory getProxyFactory() {
  24. return proxyFactory;
  25. }
  26. // createProxy,说明创建出来的就是代理对象,并且代理对象是 EnhancedResultObjectProxyImpl
  27. public static Object createProxy(Object target, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
  28. final Class<?> type = target.getClass();
  29. EnhancedResultObjectProxyImpl callback = new EnhancedResultObjectProxyImpl(type, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
  30. Object enhanced = crateProxy(type, callback, constructorArgTypes, constructorArgs);
  31. PropertyCopier.copyBeanProperties(type, target, enhanced);
  32. return enhanced;
  33. }

invoke方法的执行

  1. // 执行获取属性 EnhancedResultObjectProxyImpl.invoke方法
  2. @Override
  3. public Object invoke(Object enhanced, Method method, Method methodProxy, Object[] args) throws Throwable {
  4. final String methodName = method.getName();
  5. try {
  6. synchronized (lazyLoader) {
  7. if (WRITE_REPLACE_METHOD.equals(methodName)) {
  8. Object original;
  9. if (constructorArgTypes.isEmpty()) {
  10. original = objectFactory.create(type);
  11. } else {
  12. original = objectFactory.create(type, constructorArgTypes, constructorArgs);
  13. }
  14. PropertyCopier.copyBeanProperties(type, enhanced, original);
  15. if (lazyLoader.size() > 0) {
  16. return new JavassistSerialStateHolder(original, lazyLoader.getProperties(), objectFactory, constructorArgTypes, constructorArgs);
  17. } else {
  18. return original;
  19. }
  20. } else {
  21. // 判断是否加载所有属性 || 方法是否懒加载 对应了两个配置setting
  22. if (lazyLoader.size() > 0 && !FINALIZE_METHOD.equals(methodName)) {
  23. if (aggressive || lazyLoadTriggerMethods.contains(methodName)) {
  24. lazyLoader.loadAll();
  25. } else if (PropertyNamer.isSetter(methodName)) {
  26. final String property = PropertyNamer.methodToProperty(methodName);
  27. lazyLoader.remove(property);
  28. } else if (PropertyNamer.isGetter(methodName)) {
  29. // 最终执行的逻辑
  30. final String property = PropertyNamer.methodToProperty(methodName);
  31. // 判断是否为延迟加载的属性
  32. if (lazyLoader.hasLoader(property)) {
  33. // 延迟加载单个属性
  34. lazyLoader.load(property);
  35. }
  36. }
  37. }
  38. }
  39. }
  40. // 执行原方法
  41. return methodProxy.invoke(enhanced, args);
  42. } catch (Throwable t) {
  43. throw ExceptionUtil.unwrapThrowable(t);
  44. }
  45. }