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方法的时候,走代理,拿到之前准备好的加载环境进行懒加载。