文章目录
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
-
发现,并不是一下便将全部的信息的查询出来。