mybatis原理分析(九)---懒加载

1.概述

懒加载是为改善,解析对象属性时大量的嵌套子查询的并发问题。设置懒加载后,只有在使用指定属性时才会加载,从而分散SQL请求。

本文将从如何使用懒加载,懒加载触发条件,懒加载失效,懒加载原理方面展开。

2.懒加载使用

在嵌套子查询中指定 fetchType=“lazy” 即可设置懒加载。在满足触发条件的时候才会实现真正的加载。

在这里插入图片描述

也可以在mybatis的配置文件里面修改懒加载的全局开关,默认是false。不过一般不这么做。

在这里插入图片描述

3.懒加载触发条件

先编写如下的测试代码

@Test
public void test() {
  SqlSessionFactory build = new SqlSessionFactoryBuilder().build(LazyTest.class.getResourceAsStream("/mybatis-config.xml"));
  SqlSession sqlSession = build.openSession();
  Blog blog = sqlSession.selectOne("selectBlogById", 1);
  System.out.println(blog);
  List<Comment> comments = blog.getComments();
  System.out.println(blog);
}

debug来看一看是否触发了懒加载

断点定位在第一个sout的代码处,此时出现了奇怪的现象明明设置了懒加载,为什么还么有执行getComments方法的时候,就已经加载了Comments?

在这里插入图片描述

原因是在debug模式下,会执行toString方法。而执行toString方法会导致懒加载的触发。

在Configuration源码中可以看到这样一个集合在这里插入图片描述

表示执行了这4个方法都会执行懒加载。

此时修改我们的测试代码,将这个集合设置为空

@Test
  public void test() {
    SqlSessionFactory build = new SqlSessionFactoryBuilder().build(LazyTest.class.getResourceAsStream("/mybatis-config.xml"));
    SqlSession sqlSession = build.openSession();
    Configuration configuration = build.getConfiguration();
    configuration.setLazyLoadTriggerMethods(new HashSet<>());//设置为空
    Blog blog = sqlSession.selectOne("selectBlogById", 1);
    System.out.println(blog);
    List<Comment> comments = blog.getComments();
    System.out.println(blog);
  }
}

再次debug看一看结果

在这里插入图片描述

现在comments并没有被加载出来。当执行完getComments后结果如下

在这里插入图片描述

comments的内容有了。说明实现了懒加载。

根据上面的debug的结果,可以知道懒加载的触发条件是

执行"equals", “clone”, “hashCode”, "toString"这4个方法。或者是执行getComments,获取懒加载的对象时。

还有一个mybatis的全局配置aggressiveLazyLoading,设置了该属性为true,那么调用该对象的任意方法,都会导致懒加载的触发。

在这里插入图片描述

4.懒加载失效

4.1 set

在懒加载执行之前,调用set方法会导致懒加载的失效。

测试代码如下:

@Test
public void test2() {
  SqlSessionFactory build = new SqlSessionFactoryBuilder().build(LazyTest.class.getResourceAsStream("/mybatis-config.xml"));
  SqlSession sqlSession = build.openSession();
  Configuration configuration = build.getConfiguration();
  configuration.setLazyLoadTriggerMethods(new HashSet<>());
  Blog blog = sqlSession.selectOne("selectBlogById", 1);
  System.out.println(blog);
  blog.setComments(new ArrayList<>());
  List<Comment> comments = blog.getComments();
  System.out.println(blog);
}

和上面一样,代码定位在第一个sout处,结果如下

在这里插入图片描述

此时再执行完getComments并没有触发懒加载。说明懒加载失效了。

在这里插入图片描述

4.2 序列化

当对象经过序列化和反序列化之后,默认不在支持懒加载。

测试代码如下

@Test
public void test3() throws IOException, ClassNotFoundException {
  SqlSessionFactory build = new SqlSessionFactoryBuilder().build(LazyTest.class.getResourceAsStream("/mybatis-config.xml"));
  SqlSession sqlSession = build.openSession();
  Configuration configuration = build.getConfiguration();
  configuration.setLazyLoadTriggerMethods(new HashSet<>());
  Blog blog = sqlSession.selectOne("selectBlogById", 1);
  System.out.println(blog);

  ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(new File("/Users/gongsenlin/code/IdeaWorkspace/mybatis-3-mybatis-3.5.4/src/MyTest/java/com/gongsenlin/LazyTest/blog.txt")));
  objectOutputStream.writeObject(blog);//序列化

  ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(new File("/Users/gongsenlin/code/IdeaWorkspace/mybatis-3-mybatis-3.5.4/src/MyTest/java/com/gongsenlin/LazyTest/blog.txt")));
  Blog blog1 = (Blog) objectInputStream.readObject();// 反序列化

  List<Comment> comments = blog1.getComments();
  System.out.println(blog);
}

在执行getComments之前 blog的内容如下:

在这里插入图片描述

此时再执行序列化与反序列化之后,再执行getComments 出现了异常

在这里插入图片描述

找不到Configuration这个类。configuraton factory为空。

我们需要在全局参数中设置configurationFactory类

在这里插入图片描述

测试代码改为如下:

private static Configuration configuration;

//序列化导致懒加载失效
@Test
public void test3() throws IOException, ClassNotFoundException {
  SqlSessionFactory build = new SqlSessionFactoryBuilder().build(LazyTest.class.getResourceAsStream("/mybatis-config.xml"));
  SqlSession sqlSession = build.openSession();
  configuration = build.getConfiguration();
  configuration.setLazyLoadTriggerMethods(new HashSet<>());
  Blog blog = sqlSession.selectOne("selectBlogById", 1);
  System.out.println(blog);

  ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(new File("/Users/gongsenlin/code/IdeaWorkspace/mybatis-3-mybatis-3.5.4/src/MyTest/java/com/gongsenlin/LazyTest/blog.txt")));
  objectOutputStream.writeObject(blog);//序列化

  ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(new File("/Users/gongsenlin/code/IdeaWorkspace/mybatis-3-mybatis-3.5.4/src/MyTest/java/com/gongsenlin/LazyTest/blog.txt")));
  Blog blog1 = (Blog) objectInputStream.readObject();// 反序列化

  List<Comment> comments = blog1.getComments();
  System.out.println(blog1);
}

public static class ConfigurationFactory {
  public static Configuration getConfiguration() {
    return configuration;
  }
}

这样懒加载就成功了。采用JAVA原生序列化是可以正常执行懒加载的。其原理是将懒加载所需参数以及配置一起进行序列化,反序列化后在通过configurationFactory获取configuration构建执行环境。

在这里插入图片描述

5. 懒加载实现原理

回到第一个测试代码,debug调试看看源码中做了些什么,实现了懒加载。

@Test
public void test() {
  SqlSessionFactory build = new SqlSessionFactoryBuilder().build(LazyTest.class.getResourceAsStream("/mybatis-config.xml"));
  SqlSession sqlSession = build.openSession();
  Configuration configuration = build.getConfiguration();
  configuration.setLazyLoadTriggerMethods(new HashSet<>());
  Blog blog = sqlSession.selectOne("selectBlogById", 1);
  System.out.println(blog);
  List<Comment> comments = blog.getComments();
  System.out.println(blog);
}

断点定位在selectOne方法处,跟进源码

在上一篇博客中介绍了结果集处理的流程。所以详细的步骤就不说明了。不太清楚的读者可以看一下上一篇博客。

在处理属性映射applyPropertyMappings的时候,会处理到comments映射

在这里插入图片描述

会执行getPropertyMappingValue方法,根据属性名来获取属性值。

在这里插入图片描述

发现它是个子查询,则会调用getNestedQueryMappingValue

在这里插入图片描述

红色这块做了懒加载的一些准备工作

resultLoader里面的属性如下:

在这里插入图片描述

记录了这一次子查询所需要的信息,mappedStatement、参数、返回类型等等。

判断是否懒加载,这里是需要懒加载的 执行lazyLoad.addLoader

在这里插入图片描述

loaderMap 记录了需要懒加载的属性,value是LoadPair,这是用于在对象序列化反序列化后,重新构建加载环境用的。

之后调用getComments方法的时候

通过对Bean的代理,重写属性的getXxx方法。在获取属性前先判断属性是否加载?然后加载之。

在这里插入图片描述

拿出刚从执行selectOne时候放入的LoadPair 加载环境 执行load方法

在这里插入图片描述

一系列的验证,然后就是执行this.resultLoader.loadResult()方法去执行懒加载。之后的逻辑就很简单了,就是之前博客中分析过的查找的逻辑。
在这里插入图片描述

所以懒加载的关键就是在于执行主查询的时候,对需要懒加载的属性,保存查询它需要的相关环境 即LoadPair到一个map当中。

在之后调用get方法的时候,走代理,拿到之前准备好的加载环境进行懒加载。

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值