详解MyBatis延迟加载是如何实现的
引言
- MyBatis 的延迟加载(懒加载)特性允许在需要使用关联对象数据时才进行加载,而不是在执行主查询时就加载所有相关数据。这种机制可以提高应用程序的性能,特别是当关联数据庞大或关联层次较深时。我们将通过以下几个方面来深入了解MyBatis的延迟加载实现机制。
配置延迟加载
-
要在MyBatis中启用延迟加载,需要在配置文件
mybatis-config.xml
中进行相关设置: -
<settings> <!-- 开启全局的延迟加载 --> <setting name="lazyLoadingEnabled" value="true"/> <!-- 将积极加载改为消极加载(即延迟加载) --> <setting name="aggressiveLazyLoading" value="false"/> <!-- 当开启延迟加载时,每个属性都会延迟加载 --> <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/> </settings>
-
lazyLoadingEnabled
控制是否启用延迟加载。 -
aggressiveLazyLoading
控制是否对所有关联对象进行加载,设置为false
可以避免加载不必要的关联对象。 -
lazyLoadTriggerMethods
指定哪些方法触发加载属性,通常保持默认即可。
实现原理
- MyBatis 的延迟加载主要依赖于代理对象。当配置了延迟加载后,MyBatis 会为需要延迟加载的属性生成一个代理对象,当访问这个属性时,代理对象负责执行实际的加载操作。
步骤概述
- 结果映射: 在结果映射时,对于配置了延迟加载的关联属性,MyBatis 不会立即执行关联查询,而是为该属性创建一个代理对象。
- 代理触发: 当访问这个属性的方法时,比如
get
方法,代理对象会触发实际的关联查询。 - 查询加载: 代理对象执行关联查询,并将查询结果设置到目标对象的属性中,替换掉自身。
代码演示
-
以一对多关系为例,配置延迟加载:
-
<resultMap id="blogResultMap" type="Blog"> <collection property="posts" ofType="Post" select="selectPostsForBlog" column="id" fetchType="lazy"/> </resultMap> <select id="selectBlog" resultMap="blogResultMap"> SELECT * FROM blog WHERE id = #{id} </select> <select id="selectPostsForBlog" parameterType="int" resultType="Post"> SELECT * FROM post WHERE blog_id = #{id} </select>
-
这里,
<collection>
中的fetchType="lazy"
指示MyBatis为posts
属性创建代理对象,以实现延迟加载。
源码解析
-
在MyBatis中,延迟加载的实现涉及到以下几个关键类:
SqlSessionManager
: 作为SQL会话的管理器,它负责创建SqlSession
。Configuration
: 包含了MyBatis的所有配置信息,包括延迟加载的设置。Executor
: 执行器,负责执行SQL命令,延迟加载的触发最终会调用执行器来执行关联查询。ProxyFactory
: 代理工厂,用于创建延迟加载的代理对象。
-
延迟加载的代理对象主要通过Java的动态代理实现。在访问代理对象的方法时,动态代理会拦截这个调用,并判断是否需要触发延迟加载。如果需要,则执行实际的查询并加载数据,然后将结果设置到目标对象中。
-
以下是一个简化的示例来说明代理对象如何拦截方法调用并触发加载:
-
public class LazyLoadingProxy implements InvocationHandler { private Object target; private boolean loaded; public LazyLoadingProxy(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (!loaded && isLoadMethod(method)) { loadObject(); loaded = true; } return method.invoke(target, args); } private boolean isLoadMethod(Method method) { // 判断方法是否触发加载 return "getPosts".equals(method.getName()); } private void loadObject() { // 执行加载逻辑,比如执行SQL查询 } }
-
在上面的代码中,
LazyLoadingProxy
是一个动态代理类,它在方法调用时判断是否需要加载数据,并在必要时进行加载。这个简化的例子演示了延迟加载的基本思想。
总结
- MyBatis的延迟加载特性通过动态代理和配置控制,实现了按需加载关联数据的能力。通过延迟加载,可以优化应用程序的性能,特别是在处理复杂关系和大量数据时。虽然延迟加载增加了实现的复杂度,但MyBatis通过提供灵活的配置和强大的映射机制,使得管理这种复杂度成为可能。