zoukankan      html  css  js  c++  java
  • 浅析mybatis里的懒加载

    一、需求背景

      需求:比如查询订单信息,需要查询出是谁下单的,就是需要关联查询出用户信息。

      第一种方法:我们直接关联查询出所有订单和用户的信息

      第二种方法:分步查询,首先查询出所有的订单信息,然后如果需要用户的信息,我们在根据查询的订单信息去关联用户信息

      对应分析:

      如果使用第一种方法:这里我们一次查询出所有的信息,需要什么信息的时候直接从查询的结果中筛选。但是如果订单和用户表都比较大的时候,这种关联查询肯定比较耗时。

      我们的需求是有时候需要关联查询用户信息,这里不是一定需要用户信息的。即有时候不需要查询用户信息,我们也查了,程序进行了多余的耗时操作。

      而第二种方法:这里两步都是单表查询,执行效率比关联查询要高很多。分为两步,如果我们不需要关联用户信息,那么我们就不必执行第二步,程序没有进行多余的操作。

      这第二种方法就是mybatis的懒加载。

    二、什么是mybatis懒加载

    1、懒加载是什么

      通俗的讲就是按需加载,我们需要什么的时候再去进行什么操作。

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

      在mybatis中,resultMap可以实现高级映射(使用association、collection实现一对一及一对多映射),association、collection具备延迟加载功能。

      对象模型就是一个订单中持有一个对用户的引用。

      当查询订单信息时,暂时不加载用户信息,就延迟加载(懒加载)。

    2、什么情况下才能使用懒加载呢?

      上面的情况是无法实现懒加载的。因为是连接查询,所以在查询时只是执行了一次sql语句,就查询所有的数据。

      这种情况可能出现延迟加载,第一次查询结束之后,不在执行第二次查询。

    三、开启懒加载

    1、如何开启懒加载

      查看文档

    <!-- 开启懒加载配置 -->
    <settings>
        <!-- 全局性设置懒加载。如果设为‘false',则所有相关联的都会被初始化加载。 -->
        <setting name="lazyLoadingEnabled" value="true"/>
    
        <!-- 当设置为‘true'的时候,懒加载的对象可能被任何懒属性全部加载。否则,每个属性都按需加载。 -->
        <setting name="aggressiveLazyLoading" value="false"/>
    </settings>

    2、测试查看具体查询流程

      测试一下看具体流程是怎么样的

    @Test
    public void testLazy(){
        String statement = "com.ys.lazyload.OrdersMapper.getOrderByOrderId";
        //创建OrdersMapper对象,mybatis自动生成mapepr代理对象
        OrdersMapper orderMapper = session.getMapper(OrdersMapper.class);
        List<Orders> orders = orderMapper.getOrderByOrderId();//第一步
        for(Orders order : orders){
            System.out.println(order.getUser());//第二步
        }
        session.close();
    }

      当我们运行到第一步时,发出了第一次查询所有订单信息sql语句:select * from orders。注意只是查询订单信息,还没有进行关联查询。

      当我们运行到第二步时,已经执行了一次for循环,因为我们需要用户信息,故发出了根据用户id查询用户信息的sql语句。

      注意:如果用户信息有多条,这里并不会发出多条sql语句,这是由于mybatis的一级缓存的原因。

    3、总结

    (1)启动懒加载,mybatis初始化返回类型的时候,会返回一个cglib代理对象,该对象的关联对象(例如一对多,多对一)相关信息就会在loadpair里边,并且添加到loadmap中,cglib对象会过滤get,set ,is,"equals", "clone", "hashCode", "toString"触发方法,然后才会调用loadpair来加载关联对象的值。所以我们必须在进行懒加载的时候必须要导入相应的jar包,不然会报错。

      但是注意,新版的MyBatis已经不需要引入这2个包了。所以现在的一般都不需要引入了。

    (2)其实通过上面的例子,我们很好理解懒加载的原理,就是按需加载。我们需要什么信息的时候再去查。而不是一次性查询所有的。将复杂的关联查询分解成单表查询,然后通过单表查询的结果去关联查询。

    (3)那么不用mybatis的懒加载我们也可是实现上面的例子:

      一、定义两个mapper方法:1、查询订单列表;2、根据用户 id 查询用户信息

      二、先去查询第一个mapper方法,获取订单信息列表,然后放入到一个集合中

      三、如果需要用户信息,那么在程序中,我们可以遍历订单信息,得到用户id,然后通过id去查询用户信息。

      这与mybatis懒加载的区别就是,mybatis是在mapper.xml文件中配置好关联关系了,我们直接调用就好了。而自己实现的原理就是手动去建立关联关系。

    四、懒加载具体分析研究

      在实际使用中,我们会经常性的涉及到多表联合查询,但是有时候,并不会立即用到所有的查询结果,我来举两个例子:

    • 例如,查询一批笔记本电脑的进货明细,而不直接展示每列明细对应电脑配置或者价格等的详细信息,等到用户需要取出某笔记本相关的详细信息的时候,再进行单表查询
    • 再例如 ,银行中,某个用户拥有50个账户(打比方),再我们查询这个而用户的信息,这个用户下所有账户的详细信息很显然,在使用的时候再查询才是比较合理的

      针对这样一种情况,延迟加载这一种机制就出现了,延迟加载(懒加载)顾名思义,就是对某种信息推迟加载,这样的技术也就帮助我们实现了 “按需查询” 的机制,在一对多,或者多对多的情况下

      既然提到了延迟加载,当然顺便提一句立即加载,它的含义就是不管是否用户需要,一调用,则马上查询,这种方式,适合与多对一,或者一对一的情况下。

    1、如何实现延迟加载

      我们选择 查询账户,然后延迟加载用户的信息

    (1)修改AccountMapper.xml

      首先需要修改的就是账户的映射配置文件,可以看到我们在查询时,依旧定义了一个 resultMap 先封装了 Account ,然后通过association 进行关联 User,其中使用的就是 select 和 column 实现了延迟加载用户信息

    • select 用来指定延迟加载所需要执行的 SQL 语句,也就是指定 某个SQL映射文件中的某个select标签对的 id,在这里我们指定了用户中通过id查询信息的方法
    • column 是指关联的用户信息查询的列,在这里也就是关联的用户的主键即,id
    <mapper namespace="cn.ideal.mapper.AccountMapper">
        <!-- 定义封装 Account和User 的resultMap -->
        <resultMap id="userAccountMap" type="Account">
            <id property="id" column="id"></id>
            <result property="uid" column="uid"></result>
            <result property="money" column="money"></result>
            <!-- 配置封装 User 的内容
                select:查询用户的唯一标识
                column:用户根据id查询的时候,需要的参数值
            -->
            <association property="user" column="uid" javaType="User" select="cn.ideal.mapper.UserMapper.findById"></association>
        </resultMap>
    
        <!-- 根据查询所有账户 -->
        <select id="findAll" resultMap="userAccountMap">
            SELECT * FROM account
        </select>
    </mapper>

    (2)第一次测试代码

      我们只执行一下账户的查询所有方法,看一下,是否能够实现我们的效果

    @Test
    public void testFindAll(){
        List<Account> accounts = accountMapper.findAll();
    }

      可以看到,三条 SQL 语句都执行了,这是为什么呢?

      这是因为,我们在测试方法之前,需要开启延迟加载功能

    (3)延迟加载功能

      我们可以去官网,如何配置开启这样一个功能

      经过查阅文档,我们知道了,如果想要开始延迟加载功能,就需要在总配置文件 SqlMapConfig.xml 中配置 setting 属性,也就是将延迟加载 lazyLoadingEnable 的开关设置成 true ,由于是按需加载,所以还需要将积极加载修改为消极加载,也就是将 aggressiveLazyLoading 改为 false

      当然,由于我这里导入的 MyBatis 版本为 3.4.5 所以这个值默认就是 false 实际上不用设置也可以,不过我们还是写出来

    <settings>
        <setting name="lazyLoadingEnabled" value="true"/>
         <setting name="aggressiveLazyLoading" value="false"></setting>
    </settings>

    (4)再次测试,仍然只执行查询方法

      这一次果然只执行了一条查询 account 的命令

      那么当用户想要查看到,每个账户对应下的用户的时候呢?这也就是按需查询,只需要在测试时,加入对应获取方法就可以了

    @Test
    public void testFindAll(){
        List<Account> accounts = accountMapper.findAll();
        for (Account account : accounts){
            System.out.println("----------------------------");
            System.out.println(account);
            System.out.println(account.getUser());
        }
    }

      可以看到,我们延迟加载的目的达到了。

    2、总结

      上面的测试,我们已经实现了延迟加载,简单的总结一下步骤:

    ①:执行对应的 mapper 方法,也就是上例中执行 Mapper 中 id 值为 findAll 的对应 SQL配置,只查询到账户的信息

    ②:在程序中,遍历查询到的 accounts ,调用 getUser() 方法时,开始进行延迟加载

    ③:进行延迟加载,调用映射文件中 id 值为 findById 的对应 SQL配置,获取到对应用户的信息

      可以看到,我们之前通过使用 左外连接等的 SQL书写方式,直接就可以查询到多张表

    SELECT u.*,a.id as aid,a.uid,a.money FROM user u LEFT OUTER JOIN account a on u.id = a.uid;

      但是我们可以通过延迟加载,实现我们按需查询的需求。

      综上所述,在使用的时候,先执行简单的 SQL,然后再按照需求加载查询其他信息。

  • 相关阅读:
    Leetcode题库——7.反转整数
    (tomcat)tomcat启动过慢
    (tomcat)查看tomcat安装路径
    (JDK)cmd中只能执行java不能执行javac命令
    (课)学习进度报告二
    (数据导入)csv文件数据导入数据库
    (编码转换)转换文件编码
    (python开发)用cmd下载Python的第三方库所遇问题及解决方法
    (课)学习进度报告一
    (课)淘宝网质量属性场景
  • 原文地址:https://www.cnblogs.com/goloving/p/14854902.html
Copyright © 2011-2022 走看看