<返回更多

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

2023-04-28  微信公众号   Java极客技术
加入收藏


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

什么是延迟加载?

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

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

我们来举个简单的例子

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

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

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

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

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

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

延迟加载分类

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

全局延迟加载

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

修改内容如下:

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

局部延迟加载

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

实现方式如下:

 
<!-- 开启⼀对多 延迟加载 -->
<resultMap id="userMap" type="user">
    <id column="id" property="id"></id>
    <result column="username" property="username"></result>
    <result column="password" property="password"></result>
    <result column="birthday" property="birthday"></result>
<!--
fetchType="lazy" 懒加载策略
fetchType="eager" ⽴即加载策略
-->
    <collection property="orderList" ofType="order" column="id"
        select="com.lagou.dao.OrderMApper.findByUid" fetchType="lazy">
    </collection>
</resultMap>
<select id="findAll" resultMap="userMap">
    SELECT * FROM `user`
</select>

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

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

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

Spring配置加载

 
public class Configuration {
        /**
         * aggressiveLazyLoading:
         * 当开启时,任何⽅法的调⽤都会加载该对象的所有属性。否则,每个属性会按需加载(参考lazyLoadTriggerMethods).
         * 默认为true
         */
        protected boolean aggressiveLazyLoading;
        /**
         * 延迟加载触发⽅法
         */
        protected Set<String> lazyLoadTriggerMethods = new HashSet<String>(Arrays.asList(new String[]{"equals", "clone", "hashCode", "toString" }));
        /**
         * 是否开启延迟加载
         */
        protected boolean lazyLoadingEnabled = false;
 
        /**
         * 默认使⽤JAVAssist代理⼯⼚
         *
         * @param proxyFactory
         */
        public void setProxyFactory(ProxyFactory proxyFactory) {
            if (proxyFactory == null) {
                proxyFactory = new JavassistProxyFactory();
            }
            this.proxyFactory = proxyFactory;
        }
        //省略...
    }

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

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

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

为什么需要延迟加载

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

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

但是缺点也很明显:

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

声明:本站部分内容来自互联网,如有版权侵犯或其他问题请与我们联系,我们将立即删除或处理。
▍相关推荐
更多资讯 >>>