Mybatis 框架学习15 - 延迟查询

31 篇文章 0 订阅
18 篇文章 0 订阅

Mybatis 中的延迟加载

1. 引入

首先,这里有一个问题:

在一对多种中,当我们有一个用户,他有 100 个账户。

  • 在查询用户的时候,要不要把关联的账户查出来?

  • 在查询账户的时候,要不要把关联的用户查出来?

从第一个问题来说,如果有一个用户,他有 100 个账户,我们在查用户的时候,同时就把 100 个账户中查询出来,那么我们在内存中的使用就是这样子的。

  • java 会从创建一个对象,在内存中开辟一个空间用来存放这个对象,而这个对象中有一个 accounts 列表,列表中有 100 个 account 对象。那么这样的情况下,如果我们只需要查询用户,但是我们在用户对象 (User)关联了 100 个账户,我们在查询的时候,就会将这个 100 个对象全部查询出来。这个对象中创建了一个 List 对象,其中存放了 100 个 account对象的信息,而这 100 个 account 对象对我们而言是没有用处的,这样就造成了内存的极大的浪费。
  • 这就意味着,在我们不用的时候,这个装有 100 个 account 信息的 List 对象是完全不应该查出来的。
  • 那么这个时候又有一个新的问题,当我们需要使用的使用的时候,你有没有查出来,那么我们岂不是用不了了。那么这个时候,我们就应该想到一个概念。
  • 在查询用户时,用户下的账户信息,应该是什么时候使用,什么时候查询。

而对于第二个问题,如果我们查询的时候只查询出来了账户信息,这就意味着谁都看不懂,所以

  • 在查询账户,账户的所属用信息应该是一起随着账户的查询一起查询出来。

上面的这个例子,就意味着不同的查询情况,我们的查询时机也应该是不同的。

2. 延迟加载和立即加载

2.1 概念

什么是延时加载

在真正使用数据时才发起查询,不用的时候不查询。按需加载(懒加载)。

好处:

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

坏处:

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

不管用不用,只要一调用方法,马上发起查询。

好处:

  • 一下子将主表和从表的所有信息全部查出,查询出以后不会对用户的体验造成很大的影响。

坏处:

  • 如果从表的数据量过大,而从表的数据又用不上的时候,可能会造成内存空间的浪费。

2.2 何时使用

在对应的四种表关系中:一对多、多对一、一对一、多对多

这四种表关系可以分类成两类:

一对多,多对多:通常情况下,采用延时加载。比如有用户想要查询他的账单;用户想要查询他的账户…由于查询出来的是一个集合,可能会造成资源的浪费,所以在需要的情况,我们才回去查询这个集合。

多对一(尤其是 Mybatis 还没有多对一的概念)、一对一:通产情况下,都是采用立即加载。因为查询一个人的时候,就想知道他的身份信息。

3. 延时加载的使用

3.1 实现需求

需求:

查询账户(Account)信息并且关联查询用户(User)信息。如果先查询账户(Account)信息即可满足要求,当我们需要查询用户(User)信息时再查询用户(User)信息。把对用户(User)信息的按需去查询就是延时加载。

这里使用的项目的例子是一对多(一对一)查询的例子。

3.2 一对一环境下实现延时加载

这里我们需要对之前 (一对多)例子进行一定的修改:

  • 删除 AccountUser 类
  • 删除 IAccountDao 接口下的 findAllAccount 方法
  • 删除 TestAccount 测试类下的 testFindAllAccount 方法

修改完成之后,开始进行延迟加载操作需要的配置

  • 首先打开 IAccountDao.xml 这个映射配置文件,将定义封装的account和user的resultMap进行如下修改:

    <!-- 定义封装account和user的resultMap -->
        <resultMap id="accountUserMap" 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="com.itheima.dao.IUserDao.findById"></association>
        </resultMap>
    

    这里的 association 标签配置的封装 user 内容,这里使用的 select 属性是告知 Mybatis 框架通过 IUserDao 中的哪种方法来进行 User 表中数据的查询。

  • 将 IAccountDao.xml 这个映射配置文件中的 findAll 方法的 Sql 语句进行如下修改:

    select * from account;
    

    这里我们是要将 account 的表中的全部数据首先查出来,然后根据需求再将查出 User 表中的信息。

  • 然后需要对 IUserDao.xml 映射配置文件中的配置内容进行修改,首先修改映射结果集封装属性:

    <!-- 定义User的resultMap-->
        <resultMap id="userAccountMap" type="user">
            <id property="id" column="id"></id>
            <result property="username" column="username"></result>
            <result property="address" column="address"></result>
            <result property="sex" column="sex"></result>
            <result property="birthday" column="birthday"></result>
            <!-- 配置user对象中accounts集合的映射 -->
            <collection property="accounts" ofType="account">
    			<id column="aid" property="id"></id>
    			<result column="uid" property="uid"></result>
    			<result column="money" property="money"></result>
    		</collection>
        </resultMap>
    
  • 由于 IUserAccount.xml 中的 findAll 方法借助了 IUserDao 中 findById 方法,所以 IUserDao.xml 中的 findById 方法的 SQL 语句需要修改成如下:

    select * from user where id = #{uid};
    
  • 最后,在 AccountTest 测试类中进行测试:

    /**
         * 测试查询所有
         */
        @Test
        public void testFindAll() {
            List<Account> accounts = accountDao.findAll();
            for(Account account : accounts) {
                System.out.println("----------------- 一个 account 的信息 ------------------");
                System.out.println(account);
                System.out.println(account.getUser());
            }
        }
    
    
  • 以下是日志文件记录的结果:

    2019-09-06 19:35:33,560 164    [           main] DEBUG ansaction.jdbc.JdbcTransaction  - Opening JDBC Connection
    2019-09-06 19:35:34,412 1016   [           main] DEBUG source.pooled.PooledDataSource  - Created connection 758119607.
    2019-09-06 19:35:34,415 1019   [           main] DEBUG theima.dao.IAccountDao.findAll  - ==>  Preparing: select * from account 
    2019-09-06 19:35:34,433 1037   [           main] DEBUG theima.dao.IAccountDao.findAll  - ==> Parameters: 
    2019-09-06 19:35:34,449 1053   [           main] DEBUG .itheima.dao.IUserDao.findById  - ====>  Preparing: select * from user where id = ? 
    2019-09-06 19:35:34,449 1053   [           main] DEBUG .itheima.dao.IUserDao.findById  - ====> Parameters: 41(Integer)
    2019-09-06 19:35:34,452 1056   [           main] DEBUG .itheima.dao.IUserDao.findById  - <====      Total: 1
    2019-09-06 19:35:34,453 1057   [           main] DEBUG .itheima.dao.IUserDao.findById  - ====>  Preparing: select * from user where id = ? 
    2019-09-06 19:35:34,453 1057   [           main] DEBUG .itheima.dao.IUserDao.findById  - ====> Parameters: 45(Integer)
    2019-09-06 19:35:34,454 1058   [           main] DEBUG .itheima.dao.IUserDao.findById  - <====      Total: 1
    2019-09-06 19:35:34,455 1059   [           main] DEBUG theima.dao.IAccountDao.findAll  - <==      Total: 3
    
  • 然后发现,Mybatis 是同时将两个表中的数据全部查询出来的。

  • 这个原因是因为我们没有开启 Mybatis 的延时查询。需要在主配置文件中开启延时查询

        <!--配置参数-->
        <settings>
            <!--开启Mybatis支持延迟加载-->
            <setting name="lazyLoadingEnabled" value="true"/>
            <setting name="aggressiveLazyLoading" value="false"></setting>
        </settings>
    
  • 当我们开启延时之后,再次运行方法,会发现,并不是一股脑地出来的:

    在这里插入图片描述

    至于为什么最后还是进行了查询,这时因为在后来的 foreach 循环中,输出了 user 信息。

  • 如果将 foreach 循环注释:

    在这里插入图片描述
    就并没有查询 user 表中的信息。

以上就是在一对一的方式下实现延迟加载。

3.3 一对多环境下实现延时加载

步骤类似于 一对一环境下实现延时加载:

  • 首先,重新定义I UserDao.xml 中的映射结果集:

    <!-- 定义User的resultMap-->
        <resultMap id="userAccountMap" type="user">
            <id property="id" column="id"></id>
            <result property="username" column="username"></result>
            <result property="address" column="address"></result>
            <result property="sex" column="sex"></result>
            <result property="birthday" column="birthday"></result>
            <!-- 配置user对象中accounts集合的映射 -->
            <collection property="accounts" ofType="account" select="com.itheima.dao.IAccountDao.findAccountByUid" column="id"></collection>
        </resultMap>
    

    这里的 collection 说明封装的复杂结果集为一个容器。

  • 然后,修改映射文件中的 findAll 方法使用的 SQL 语句:

    select * from user;
    
  • 由于在查询 User 表中的信息时,需要根据情况查询查询出 Account 表中的数据,所以,需要在 IAccountDao 接口中重新增加一个方法,这个方法,通过传入的 user 表中的 id,查询 Account 表中与 User 表中数据相关联的数据。

        /**
         * 根据用户id查询账户信息
         * @param uid
         * @return
         */
        List<Account> findAccountByUid(Integer uid);
    
  • 在映射配置文件 IAccountDao.xml 中新增一个新的 select 标签:

        <!-- 根据用户id查询账户列表 -->
        <select id="findAccountByUid" resultType="account">
            select * from account where uid = #{uid}
        </select>
    

    这个标签表示当执行完查询 User 表内容之后,会根据需求,按照 uid 查询出 Account 表中的数据。

  • 在测试类中进行测试:

        /**
         * 测试查询所有
         */
        @Test
        public void testFindAll(){
            List<User> users = userDao.findAll();
            for(User user : users){
                System.out.println("-----每个用户的信息------");
                System.out.println(user);
                System.out.println(user.getAccounts());
            }
        }
    
  • 结果:

    -----每个用户的信息------
    2019-09-06 21:07:10,300 974    [           main] DEBUG o.IAccountDao.findAccountByUid  - ==>  Preparing: select * from account where uid = ? 
    2019-09-06 21:07:10,300 974    [           main] DEBUG o.IAccountDao.findAccountByUid  - ==> Parameters: 41(Integer)
    2019-09-06 21:07:10,304 978    [           main] DEBUG o.IAccountDao.findAccountByUid  - <==      Total: 2
    User{id=41, username='老王', address='北京', sex='男', birthday=Tue Feb 27 17:47:08 CST 2018}
    [Account{id=1, uid=41, money=1000.0}, Account{id=3, uid=41, money=2000.0}]
    -----每个用户的信息------
    2019-09-06 21:07:10,305 979    [           main] DEBUG o.IAccountDao.findAccountByUid  - ==>  Preparing: select * from account where uid = ? 
    2019-09-06 21:07:10,305 979    [           main] DEBUG o.IAccountDao.findAccountByUid  - ==> Parameters: 42(Integer)
    2019-09-06 21:07:10,305 979    [           main] DEBUG o.IAccountDao.findAccountByUid  - <==      Total: 0
    User{id=42, username='小二王', address='北京金燕龙', sex='女', birthday=Fri Mar 02 15:09:37 CST 2018}
    []
    -----每个用户的信息------
    2019-09-06 21:07:10,306 980    [           main] DEBUG o.IAccountDao.findAccountByUid  - ==>  Preparing: select * from account where uid = ? 
    2019-09-06 21:07:10,306 980    [           main] DEBUG o.IAccountDao.findAccountByUid  - ==> Parameters: 43(Integer)
    2019-09-06 21:07:10,306 980    [           main] DEBUG o.IAccountDao.findAccountByUid  - <==      Total: 0
    User{id=43, username='小二王', address='北京金燕龙', sex='女', birthday=Sun Mar 04 11:34:34 CST 2018}
    []
    -----每个用户的信息------
    2019-09-06 21:07:10,306 980    [           main] DEBUG o.IAccountDao.findAccountByUid  - ==>  Preparing: select * from account where uid = ? 
    2019-09-06 21:07:10,307 981    [           main] DEBUG o.IAccountDao.findAccountByUid  - ==> Parameters: 45(Integer)
    2019-09-06 21:07:10,307 981    [           main] DEBUG o.IAccountDao.findAccountByUid  - <==      Total: 1
    User{id=45, username='传智播客', address='北京金燕龙', sex='男', birthday=Sun Mar 04 12:04:06 CST 2018}
    [Account{id=2, uid=45, money=1000.0}]
    -----每个用户的信息------
    2019-09-06 21:07:10,307 981    [           main] DEBUG o.IAccountDao.findAccountByUid  - ==>  Preparing: select * from account where uid = ? 
    2019-09-06 21:07:10,308 982    [           main] DEBUG o.IAccountDao.findAccountByUid  - ==> Parameters: 46(Integer)
    2019-09-06 21:07:10,308 982    [           main] DEBUG o.IAccountDao.findAccountByUid  - <==      Total: 0
    User{id=46, username='老王', address='北京', sex='女', birthday=Wed Mar 07 17:37:26 CST 2018}
    []
    -----每个用户的信息------
    2019-09-06 21:07:10,308 982    [           main] DEBUG o.IAccountDao.findAccountByUid  - ==>  Preparing: select * from account where uid = ? 
    2019-09-06 21:07:10,308 982    [           main] DEBUG o.IAccountDao.findAccountByUid  - ==> Parameters: 48(Integer)
    2019-09-06 21:07:10,310 984    [           main] DEBUG o.IAccountDao.findAccountByUid  - <==      Total: 0
    User{id=48, username='小马宝莉', address='北京修正', sex='女', birthday=Thu Mar 08 11:44:00 CST 2018}
    []
    2019-09-06 21:07:10,310 984    [           main] DEBUG ansaction.jdbc.JdbcTransaction  - Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@6db9f5a4]
    2019-09-06 21:07:10,310 984    [           main] DEBUG source.pooled.PooledDataSource  - Returned connection 1840903588 to pool.
    
    
    
    Process finished with exit code 0
    
  • 发现,并不是一下便将全部的信息的查询出来。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值