问题引入
在一对多中,当我们有一个用户,它有100个账户。当我们在查询数据时,会遇到如下问题:
在查询用户的时候,要不要把关联的账户查出来?
在查询账户的时候,要不要把关联的用户查出来?
在查询用户时,用户下的账户信息应该是,什么时候使用,什么时候查询的。(延迟加载)
在查询账户时,账户的所属用户信息应该是随着账户查询时一起查询出来。(立即加载)
延迟加载:在真正使用数据时才发起查询,不用的时候不查询。按需加载(懒加载)。
立即加载:不管用不用,只要一调用方法,马上发起查询。
针对数据库表的关系来说:
-
一对多,多对多:通常情况下我们都是采用延迟加载。
-
多对一,一对一:通常情况下我们都是采用立即加载。
准备
搭建一对多查询的Mybatis环境。可以使用User用户表和Account账户表。(一个User可以有多个Account)
1、数据库表
User表
CREATE TABLE USER (
`id` INT(11)NOT NULL AUTO_INCREMENT,
`username` VARCHAR(32) NOT NULL COMMENT '用户名',
`telephone` VARCHAR(11) NOT NULL COMMENT '手机',
`birthday` DATETIME DEFAULT NULL COMMENT '生日',
`gender` CHAR(1) DEFAULT NULL COMMENT '性别',
`address` VARCHAR(256) DEFAULT NULL COMMENT '地址',
PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
Account表
CREATE TABLE `account` (
`ID` int(11) NOT NULL COMMENT '编号',
`UID` int(11) default NULL COMMENT '用户编号',
`MONEY` double default NULL COMMENT '金额',
PRIMARY KEY (`ID`),
KEY `FK_Reference_8` (`UID`),
CONSTRAINT `FK_Reference_8` FOREIGN KEY (`UID`) REFERENCES `user` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
2、实体类
User.java
public class User implements Serializable {
private Integer id;
private String username;
private String telephone;
private Date birthday;
private String gender;
private String address;
//一对多关系映射,主表实体应该包含从表实体的集合引用
private List<Account> accounts;
//省略getter和setter方法、toString方法
}
Account.java
public class Account implements Serializable {
private Integer id;
private Integer uid;
private Double money;
//从表实体应该包含一个主表实体的对象引用
private User user;
//省略getter和setter方法、toString方法
}
3、mapper接口
public interface AccountMapper {
//查询所有账户
List<Account> findAll();
}
public interface UserMapper {
// 查询所有用户信息,同时显示出该用户下的所有账户
List<User> findAll();
// 根据id查询用户信息
User findById(Integer userId);
}
4、Mapper映射的xml文件
UserMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.coydone.mapper.UserMapper">
<!-- 定义User的resultMap-->
<resultMap id="userAccountMap" type="User">
<id property="id" column="id"></id>
<result property="username" column="username"></result>
<result property="telephone" column="telephone"></result>
<result property="birthday" column="birthday"></result>
<result property="gender" column="gender"></result>
<result property="address" column="address"></result>
<collection property="accounts" ofType="account">
<id property="id" column="aid"></id>
<result property="uid" column="uid"></result>
<result property="money" column="money"></result>
</collection>
</resultMap>
<!-- 查询所有用户 并且显示对应账户信息 此时为一对多关系-->
<select id="findAll" resultMap="userAccountMap">
SELECT u.*,a.id as aid,a.uid,a.money FROM user u LEFT OUTER JOIN account a on u.id = a.uid;
</select>
<!-- 根据id查询用户 -->
<select id="findById" parameterType="INT" resultType="User">
select * from user where id = #{uid}
</select>
</mapper>
5、测试类
我们之前一对多查询用户的方式,同时会将用户对应所有的账户信息,也查询出来。
// 测试查询所有
@Test
public void testFindAll() {
List<User> users= userMapper.findAll();
for (User user : users) {
System.out.println(user);
System.out.println(user.getAccounts());
}
}
实现延迟加载
1、修改AccountMapper.xml
首先需要修改的就是账户的映射配置文件,可以看到我们在查询时,依旧定义了一个 resultMap 先封装了 Account ,然后通过association 进行关联 User,其中使用的就是 select 和 column 实现了延迟加载用户信息。
-
select 用来指定延迟加载所需要执行的 SQL 语句,也就是指定某个SQL映射文件中的某个select标签对的id,在这里我们指定了用户中通过id查询信息的方法。
-
column 是指关联的用户信息查询的列,在这里也就是关联的用户的主键,即id。
<mapper namespace="com.coydone.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="com.coydone.mapper.UserMapper.findById"></association>
</resultMap>
<!-- 根据查询所有账户 -->
<select id="findAll" resultMap="userAccountMap">
SELECT * FROM account
</select>
</mapper>
我们只执行一下账户的查询所有方法,看一下,是否能够实现我们的效果。
@Test
public void testFindAll(){
List<Account> accounts = accountMapper.findAll();
}
可以看到,三条 SQL 语句都执行了,因为我们在测试方法之前,需要开启延迟加载功能。
2、添加延迟加载功能
官方介绍了设置延迟加载的相关信息:
如果想要开始延迟加载功能,就需要在总配置文件mybatis-config.xml 中配置 setting 属性,也就是将延迟加载 lazyLoadingEnable
的开关设置成 true ,由于是按需加载,所以还需要将积极加载修改为消极加载,也就是将 aggressiveLazyLoading
改为 false。
当然,由于我这里导入的 MyBatis 版本为 3.4.5 所以这个值默认就是 false 实际上不用设置也可以。
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"></setting>
</settings>
3、测试
@Test
public void testFindAll(){
List<Account> accounts = accountMapper.findAll();
}
这一次只执行了一条查询 account 的命令,那么当用户想要查看到,每个账户对应下的用户的时候,也就是按需查询,只需要在测试时,加入对应获取方法就可以了。
@Test
public void testFindAll(){
List<Account> accounts = accountMapper.findAll();
for (Account account : accounts){
System.out.println(account);
System.out.println(account.getUser());
}
}
总结
上面的测试,我们已经实现了延迟加载,简单的总结一下步骤:
①:执行对应的 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,然后再按照需求加载查询其他信息。
其实就是将我们原来的查询两个表的连接查询进行分解,先查询主表内容,当需要查询从表数据时,我们在根据外键查询从表中对应的数据。
评论区