[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方法時**,加載帶有延遲屬性的屬性對象。