[TOC]
**推荐使用嵌套查询映射,不要使用嵌套结果映射。嵌套查询比嵌套结果写sql简单**
*****
在现实生活中,会出现一个水杯包含一个杯盖或一个系统用户有多个系统角色的情况。
对应在数据模型当中,一个对象的成员变量是另一个对象或者一个对象集合的情况。
# 6.1.1 一对一映射
**推荐使用嵌套查询映射,不要使用嵌套结果映射**
## 6.1.1.3 关联的嵌套结果映射
**使用resultMap的association标签配置一对一映射**
将sql语句查询的结果映射到嵌套的对象中
假如:一个系统用户含有一个系统角色。
**1 sql语句:根据用户id查询用户和对应的角色信息**
```
<!-- 因为userRoleMap里设置了列前缀role_,sys_role里的列需要重命名加列前缀role_-->
<select id="selectUserAndRoleById2" resultMap="userRoleMap">
select
u.id,
u.user_name,
u.user_password,
u.user_email,
u.user_info,
u.head_img,
u.create_time,
r.id role_id,
r.role_name role_role_name,
r.enabled role_enabled,
r.create_by role_create_by,
r.create_time role_create_time
from sys_user u
inner join sys_user_role ur on u.id = ur.user_id
inner join sys_role r on ur.role_id = r.id
where u.id = #{id}
</select>
```
**2 在UserMapper.xml中写**
```
<resultMap id="userMap" type="tk.mybatis.simple.model.SysUser">
<id property="id" column="id"/>
<result property="userName" column="user_name"/>
<result property="userPassword" column="user_password"/>
<result property="userEmail" column="user_email"/>
<result property="userInfo" column="user_info"/>
<result property="headImg" column="head_img" jdbcType="BLOB"/>
<result property="createTime" column="create_time" jdbcType="TIMESTAMP"/>
</resultMap>
<!-- extends属性继承userMap映射的列 ,extends属性可以继承同一个xml中其他定义好的resultMap,-->
<resultMap id="userRoleMap" extends="userMap" type="tk.mybatis.simple.model.SysUser">
<!-- 1. resultMap属性使用别的命名空间定义好的结果映射
2. association – 一个复杂类型的关联;许多结果将包装成这种类型
嵌套结果映射 – 关联本身可以是一个 resultMap 元素,或者从别处引用一个类
-->
<association property="role" columnPrefix="role_" resultMap="tk.mybatis.simple.mapper.RoleMapper.roleMap"/>
</resultMap>
```
**3 在RoleMapper.xml写**
```
<resultMap id="roleMap" type="tk.mybatis.simple.model.SysRole">
<id property="id" column="id"/>
<result property="roleName" column="role_name"/>
<result property="enabled" column="enabled"/>
<result property="createBy" column="create_by"/>
<result property="createTime" column="create_time" jdbcType="TIMESTAMP"/>
</resultMap>
```
**4 UserMapper.java里添加对应的方法**
```
/**
* 根据用户 id 获取用户信息和用户的角色信息
*
* @param id
* @return
*/
SysUser selectUserAndRoleById2(Long id);
```
**5 在UserMapperTest.java里写**
```
@Test
public void testSelectUserAndRoleById(){
//获取 sqlSession
SqlSession sqlSession = getSqlSession();
try {
//获取 UserMapper 接口
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
SysUser user = userMapper.selectUserAndRoleById2(1001L);
//user 不为空
Assert.assertNotNull(user);
//user.role 也不为空
Assert.assertNotNull(user.getRole());
} finally {
//不要忘记关闭 sqlSession
sqlSession.close();
}
}
```
## 6.1.1.4 association标签的嵌套查询
· select: 另一个映射查询的id, MyBatis会额外执行这个查询获取嵌套对象的结果。
· column: 列名(或别名) ,** 将主查询中列的结果作为嵌套查询的参数**, 配置方式如column={prop1=col1, prop2=col2}, prop1和prop2将 作为嵌套查询的参数。
· fetchType: 数据加载方式, 可选值为lazy和eager, 分别为延迟加载和积极加载, 这个配置会覆盖全局的lazyLoadingEnabled配置
**1 在UserMapper.xml中创建如下的resultMap**
```
<resultMap id="userRoleMapSelect" extends="userMap" type="tk.mybatis.simple.model.SysUser">
<!-- column用主查询的结果列作为嵌套查询的参数,select命名空间 。
当需要多个参数时, 可以配置多个, 使用逗号隔开即可, 例如column="{id=role_id,name=role_name}"。-->
<association property="role" fetchType="lazy"
select="tk.mybatis.simple.mapper.RoleMapper.selectRoleById"
column="{id=role_id}"/>
</resultMap>
<select id="selectUserAndRoleByIdSelect" resultMap="userRoleMapSelect">
select
u.id,
u.user_name,
u.user_password,
u.user_email,
u.user_info,
u.head_img,
u.create_time,
ur.role_id
from sys_user u
inner join sys_user_role ur on u.id = ur.user_id
where u.id = #{id}
</select>
```
**2 在RoleMapper.xml中**
```
<select id="selectRoleById" resultMap="roleMap">
select * from sys_role where id = #{id}
</select>
```
**3 UserMapperTest.java中**
```
@Test //嵌套查询
public void testSelectUserAndRoleByIdSelect(){
//获取 sqlSession
SqlSession sqlSession = getSqlSession();
try {
//获取 UserMapper 接口
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
//特别注意,在我们测试数据中,id = 1L 的用户有两个角色
//由于后面覆盖前面的,因此只能得到最后一个角色
//我们这里使用只有一个角色的用户(id = 1001L)
SysUser user = userMapper.selectUserAndRoleByIdSelect(1001L);
//user 不为空
Assert.assertNotNull(user);
//user.role 也不为空
System.out.println("调用 user.equals(null)");
user.equals(null);
System.out.println("调用 user.getRole()");
Assert.assertNotNull(user.getRole());
} finally {
//不要忘记关闭 sqlSession
sqlSession.close();
}
}
```
第一个 SQL 的查询结果只有一条, 所以根据这一条数据的role_id关联了另一个查询, 因此执行了两次SQL。
*****
#### **N+1问题**
在上面的例子中是否一定会用到SysRole 呢? 如果查询的SysRole出来并没有使用, 那不就白白浪费了一次查询。
如果查询的不是1 条数据, 而是N 条数据, 那就会出现 N+1 问题,
主SQL会查询一次, 查询出N条结果, 这N条结果要各再自执行一次查询, 那就需要进行N次查询。
#### **解决N+1问题**
为了提高程序执行效率,对于不使用SysRole的查询要解决N+1问题。只在user.getRole()时执行嵌套查询。
**1 在mybatis-config.xml中增加配置**
```
<settings>
<setting value="false" name="aggressiveLazyLoading"/>
</settings>
```
aggressiveLazyLoading= true, 完整加载带有延迟加载属性的对象。
aggressiveLazyLoading= false, 调用get()方法时,加载带有延迟属性的对象。
**2 在UserMapper.xml中**
```
<resultMap id="userRoleMapSelect" extends="userMap" type="tk.mybatis.simple.model.SysUser">
<!-- fetchType属性设置这个属性对象的嵌套查询为懒加载-->
<association property="role" fetchType="lazy"
select="tk.mybatis.simple.mapper.RoleMapper.selectRoleById"
column="{id=role_id}"/>
</resultMap>
```
**3 在RoleMapper.xml中**
```
<select id="selectRoleById" resultMap="roleMap">
select * from sys_role where id = #{id}
</select>
```
**4 UserMapperTest.java**
```
@Test //嵌套查询
public void testSelectUserAndRoleByIdSelect(){
//获取 sqlSession
SqlSession sqlSession = getSqlSession();
try {
//获取 UserMapper 接口
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
//特别注意,在我们测试数据中,id = 1L 的用户有两个角色
//由于后面覆盖前面的,因此只能得到最后一个角色
//我们这里使用只有一个角色的用户(id = 1001L)
SysUser user = userMapper.selectUserAndRoleByIdSelect(1001L);
//user 不为空
Assert.assertNotNull(user);
//user.role 也不为空
System.out.println("调用 user.equals(null)");
user.equals(null);
System.out.println("调用 user.getRole()");
Assert.assertNotNull(user.getRole());
} finally {
//不要忘记关闭 sqlSession
sqlSession.close();
}
}
```
**1 注意**
MyBatis延迟加载是通过动态代理实现的, 当调用配置为延迟加载 的属性方法时, 动态代理的操作会被触发, 这些额外的操作就是通过 MyBatis的SqlSession去执行嵌套SQL的。 由于在和某些框架集成时, SqlSession 的生命周期交给了框架来管理, 因此当对象超出SqlSession生 命周期调用时, 会由于链接关闭等问题而抛出异常。 在和Spring集成时,**要确保只能在Service层调用延迟加载的属性。 当结果从Service层返
回至Controller层时, 再用get()方法获取延迟加载的属性值, 会因为SqlSession已
经关闭而抛出异常**
**2 注意**
MyBatis提供了参数 lazyLoadTriggerMethods , **当调用配置中的方法equals, clone, hashCode,toString方法时**,加载带有延迟属性的属性对象。