MyBatis的延迟加载,你知道是怎么实现的么?

今天药忘吃喽~ 2023-09-26 23:39 180阅读 0赞

延迟加载也称为懒加载、惰性加载,使用延迟加载可以提高程序的运行效率,针对数据持久层的操作,在某些特定查询的情况下去访问特定的数据库,在其他情况下可以不访问某些数据表,尽量减少 SQL 的执行,从而达到提高速度的目的,是对数据库操作的一种优化。

今天了不起来说说这个关于 Mybatis ,为什么要说 Mybatis 呢?因为现在面试的时候,除了那些最基础的,比如如何防止 SQL 注入,以及 Mybatis 的一级缓存,二级缓存之后,还有一些其他的问题,比如 Mybatis 的延迟加载,并且需要说一下延迟加载的使用场景。今天了不起就来给大家说一下这个延迟加载到底是怎么回事,以及延迟加载的使用场景。

什么是延迟加载?

延迟加载也称为懒加载、惰性加载,使用延迟加载可以提高程序的运行效率,针对数据持久层的操作,在某些特定查询的情况下去访问特定的数据库,在其他情况下可以不访问某些数据表,尽量减少 SQL 的执行,从而达到提高速度的目的,是对数据库操作的一种优化。

什么情况下会出现延迟加载的情况

我们来举个简单的例子

在一对多中,当我们有一个用户,它有个100个订单 在查询用户的时候,要不要把关联的订单查出来? 在查询订单的时候,要不要把关联的用户查出来?

那这个时候,答案就很清晰了,肯定要查出来的,但是,是我在需要的地方。

在查询用户时,用户下的订单应该是,什么时候用,什么时候查询。

在查询订单时,订单所属的用户信息应该是随着订单一起查询出来

这个时候我们就会用到延迟加载了,我用的时候,我们就查询,我们不用的时候,我们就不再继续的进行查询了。

注意:延迟加载的应用要求:关联对象的查询与主加载对象的查询必须是分别进行的select语句,不能是使用多表连接所进行的select查询。

延迟加载分类

其实延迟加载也是有区分对的,而这个区分就是,全局的延迟加载和局部的延迟加载。

全局延迟加载

全局延迟加载的实现,实际上是通过修改配置文件来进行实现的,只要改了对应的配置配置文件,重启之后,肯定直接全局实现,所有需要用到全局加载的 xml 文件,都可以进行实现了。

修改内容如下:

  1. <settings>
  2. <!--开启全局延迟加载功能-->
  3. <setting name="lazyLoadingEnabled" value="true"/>
  4. </settings>

局部延迟加载

在association和collection标签中都有⼀个fetchType属性,通过修改它的值,可以修改局部的加载策略。

实现方式如下:

  1. <!-- 开启⼀对多 延迟加载 -->
  2. <resultMap id="userMap" type="user">
  3. <id column="id" property="id"></id>
  4. <result column="username" property="username"></result>
  5. <result column="password" property="password"></result>
  6. <result column="birthday" property="birthday"></result>
  7. <!--
  8. fetchType="lazy" 懒加载策略
  9. fetchType="eager" ⽴即加载策略
  10. -->
  11. <collection property="orderList" ofType="order" column="id"
  12. select="com.lagou.dao.OrderMapper.findByUid" fetchType="lazy">
  13. </collection>
  14. </resultMap>
  15. <select id="findAll" resultMap="userMap">
  16. SELECT * FROM `user`
  17. </select>

其实在这里,我们需要注意一个内容,那就是延迟加载的时候:局部的加载策略的优先级高于全局的加载策略。

这个也是面试的时候,经常会被问到的内容。

那么我们就得来看看这个延迟加载的具体实现:

Spring配置加载

  1. public class Configuration {
  2. /**
  3. * aggressiveLazyLoading:
  4. * 当开启时,任何⽅法的调⽤都会加载该对象的所有属性。否则,每个属性会按需加载(参考lazyLoadTriggerMethods).
  5. * 默认为true
  6. */
  7. protected boolean aggressiveLazyLoading;
  8. /**
  9. * 延迟加载触发⽅法
  10. */
  11. protected Set<String> lazyLoadTriggerMethods = new HashSet<String>(Arrays.asList(new String[]{"equals", "clone", "hashCode", "toString" }));
  12. /**
  13. * 是否开启延迟加载
  14. */
  15. protected boolean lazyLoadingEnabled = false;
  16. /**
  17. * 默认使⽤Javassist代理⼯⼚
  18. *
  19. * @param proxyFactory
  20. */
  21. public void setProxyFactory(ProxyFactory proxyFactory) {
  22. if (proxyFactory == null) {
  23. proxyFactory = new JavassistProxyFactory();
  24. }
  25. this.proxyFactory = proxyFactory;
  26. }
  27. //省略...
  28. }

Spring的加载,我们已经看到了,接下来我们还得看看延迟加载代理对象创建,他都是怎么来创建的,说到创建类,那么就得找到这个 ResultSetHandler 这个类了,内部是有有个 handleResultSets 的方法,而方法内部,就有加载的过程。默认采用javassistProxy进行代理对象的创建

  1. // 创建映射后的结果对象
  2. private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
  3. // useConstructorMappings ,表示是否使用构造方法创建该结果对象。此处将其重置
  4. this.useConstructorMappings = false; // reset previous mapping result
  5. final List<Class<?>> constructorArgTypes = new ArrayList<>(); // 记录使用的构造方法的参数类型的数组
  6. final List<Object> constructorArgs = new ArrayList<>(); // 记录使用的构造方法的参数值的数组
  7. // 创建映射后的结果对象
  8. Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix);
  9. if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
  10. // 如果有内嵌的查询,并且开启延迟加载,则创建结果对象的代理对象
  11. final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
  12. for (ResultMapping propertyMapping : propertyMappings) {
  13. // issue gcode #109 && issue #149
  14. if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) {
  15. // 创建延迟加载代理对象
  16. resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
  17. break;
  18. }
  19. }
  20. }
  21. // 判断是否使用构造方法创建该结果对象
  22. this.useConstructorMappings = resultObject != null && !constructorArgTypes.isEmpty(); // set current mapping result
  23. return resultObject;
  24. }

如果你想要在你的代码中验证延迟加载是否生效的话,那么有一个很简单的方法,开启日志的 SQL 打印功能,那么就可以直接验证你的延迟加载是否生效了。

为什么需要延迟加载

其实这也是延迟加载的优点,优点如下:

先从单表查询,需要时再从关联表去关联查询,⼤⼤提⾼数据库性能,因为查询单表要比关联查询多张表速度要快。

但是缺点也很明显:

有当需要用到数据时,才会进行数据库查询,这样在大批量数据查询时,因为查询工作也要消耗时间,所以可能造成⽤户等待时间变长,造成用户体验下降。

发表评论

表情:
评论列表 (有 0 条评论,180人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Mybatis 延迟

    8.1 使用延迟加载意义 在进行数据查询时,为了提高数据库查询性能,尽量使用单表查询,因为单表查询比多表关联查询速度要快。 如果查询单表就可以满足需求,一开始先查询单表,当