MyBatis 延迟加载底层原理(2)

56 阅读3分钟

前言

我们在前提到MyBatis 延迟加载的实现依赖于 Java 的代理模式,确切地说是动态代理。在 Java 中,动态代理主要有 JDK 动态代理和 CGLIB 动态代理两种方式,MyBatis 都支持。那么延迟加载的触发条件是什么呢?

延迟加载的触发机制

MyBatis 延迟加载的触发与 ResultMap 中的 association 和 collection 标签密切相关。当查询主对象时,MyBatis 会根据 ResultMap 的配置,为关联对象创建代理对象。

在 association 和 collection 标签中,通过 select 属性指定关联对象的查询方法,fetchType 属性指定加载方式为 lazy。当调用主对象中关联对象的属性或方法时,代理对象会拦截该调用,判断关联对象是否已经加载。若未加载,则通过 Executor 执行指定的查询方法,从数据库中获取关联对象数据。

核心组件协作

MyBatis 延迟加载的实现涉及多个核心组件的协作,主要包括 ExecutorResultSetHandlerProxyFactory 等。

Executor

Executor 是 MyBatis 的执行器,负责执行 SQL 语句。在延迟加载中,当代理对象需要触发数据库查询时,会调用 Executor 的 query 方法执行关联对象的查询语句。

ResultSetHandler

ResultSetHandler 负责将数据库查询结果映射到 Java 对象。在处理关联对象时,ResultSetHandler 会根据 ResultMap 的配置,为关联对象创建代理对象。

ProxyFactory

ProxyFactory 是代理工厂,负责创建代理对象。MyBatis 根据关联对象是否实现接口,选择使用 JDK 动态代理或 CGLIB 动态代理来创建代理对象。

延迟加载的执行流程

下面详细描述 MyBatis 延迟加载的执行流程:

image.png

  1. 查询主对象:当执行主对象的查询语句时,MyBatis 通过 Executor 执行 SQL,将结果集返回给 ResultSetHandler
  2. 创建代理对象ResultSetHandler 根据 ResultMap 的配置,为关联对象创建代理对象,而不是直接加载关联对象数据。
  3. 调用关联对象方法:当调用主对象中关联对象的方法时,代理对象的 InvocationHandler 会拦截该调用。
  4. 判断是否加载:代理对象检查关联对象是否已经加载,如果未加载,则触发延迟加载。
  5. 执行关联查询:通过 Executor 执行 ResultMap 中 select 属性指定的查询方法,从数据库中获取关联对象数据。
  6. 返回结果:将查询到的关联对象数据返回给调用者。

注意事项

会话生命周期问题

MyBatis 的延迟加载依赖于 SqlSession,如果在关闭 SqlSession 后再访问延迟加载的对象,会抛出 org.apache.ibatis.executor.ExecutorException 异常。因为 SqlSession 关闭后,Executor 也会被关闭,无法再执行数据库查询操作。

性能平衡

虽然延迟加载能减少不必要的查询,但如果过度使用,可能会导致多次数据库查询,增加数据库的压力。在实际开发中,需要根据业务需求和性能测试结果,合理选择是否使用延迟加载。

总结

MyBatis 延迟加载通过动态代理模式实现,在需要访问关联对象时才触发数据库查询,有效提升了系统性能。其底层涉及 ExecutorResultSetHandlerProxyFactory 等核心组件的协作,通过一系列步骤完成延迟加载操作。在使用时,要注意 SqlSession 的生命周期和性能平衡问题