前言
我们在前提到MyBatis 延迟加载的实现依赖于 Java 的代理模式,确切地说是动态代理。在 Java 中,动态代理主要有 JDK 动态代理和 CGLIB 动态代理两种方式,MyBatis 都支持。那么延迟加载的触发条件是什么呢?
延迟加载的触发机制
MyBatis 延迟加载的触发与 ResultMap 中的 association 和 collection 标签密切相关。当查询主对象时,MyBatis 会根据 ResultMap 的配置,为关联对象创建代理对象。
在 association 和 collection 标签中,通过 select 属性指定关联对象的查询方法,fetchType 属性指定加载方式为 lazy。当调用主对象中关联对象的属性或方法时,代理对象会拦截该调用,判断关联对象是否已经加载。若未加载,则通过 Executor 执行指定的查询方法,从数据库中获取关联对象数据。
核心组件协作
MyBatis 延迟加载的实现涉及多个核心组件的协作,主要包括 Executor、ResultSetHandler、ProxyFactory 等。
Executor
Executor 是 MyBatis 的执行器,负责执行 SQL 语句。在延迟加载中,当代理对象需要触发数据库查询时,会调用 Executor 的 query 方法执行关联对象的查询语句。
ResultSetHandler
ResultSetHandler 负责将数据库查询结果映射到 Java 对象。在处理关联对象时,ResultSetHandler 会根据 ResultMap 的配置,为关联对象创建代理对象。
ProxyFactory
ProxyFactory 是代理工厂,负责创建代理对象。MyBatis 根据关联对象是否实现接口,选择使用 JDK 动态代理或 CGLIB 动态代理来创建代理对象。
延迟加载的执行流程
下面详细描述 MyBatis 延迟加载的执行流程:
- 查询主对象:当执行主对象的查询语句时,MyBatis 通过
Executor执行 SQL,将结果集返回给ResultSetHandler。 - 创建代理对象:
ResultSetHandler根据ResultMap的配置,为关联对象创建代理对象,而不是直接加载关联对象数据。 - 调用关联对象方法:当调用主对象中关联对象的方法时,代理对象的
InvocationHandler会拦截该调用。 - 判断是否加载:代理对象检查关联对象是否已经加载,如果未加载,则触发延迟加载。
- 执行关联查询:通过
Executor执行ResultMap中select属性指定的查询方法,从数据库中获取关联对象数据。 - 返回结果:将查询到的关联对象数据返回给调用者。
注意事项
会话生命周期问题
MyBatis 的延迟加载依赖于 SqlSession,如果在关闭 SqlSession 后再访问延迟加载的对象,会抛出 org.apache.ibatis.executor.ExecutorException 异常。因为 SqlSession 关闭后,Executor 也会被关闭,无法再执行数据库查询操作。
性能平衡
虽然延迟加载能减少不必要的查询,但如果过度使用,可能会导致多次数据库查询,增加数据库的压力。在实际开发中,需要根据业务需求和性能测试结果,合理选择是否使用延迟加载。
总结
MyBatis 延迟加载通过动态代理模式实现,在需要访问关联对象时才触发数据库查询,有效提升了系统性能。其底层涉及 Executor、ResultSetHandler、ProxyFactory 等核心组件的协作,通过一系列步骤完成延迟加载操作。在使用时,要注意 SqlSession 的生命周期和性能平衡问题