關於級聯查詢,會涉及到Mybatis的”N+1”問題,之後講解什麼是”N+1”問題以及怎麼解決。
主要是使用<association>
來實現關聯。
有兩種方式來實現關聯。
- 嵌套查詢:通過執行另外一個 SQL 映射語句來返回預期的複雜類型。
- 嵌套結果:使用嵌套結果映射來處理重複的聯合結果的子集。首先,讓我們來查看這個元素的屬性。你會看到,它和普通的只由 select 和
resultMap 屬性的結果映射不同。==(推薦)==
代碼準備
每個用戶User都只有一個身份證IdCard。
數據庫Sql
CREATE TABLE `tb_user` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'ID',
`username` varchar(50) NOT NULL COMMENT '姓名',
`age` tinyint(4) NOT NULL COMMENT '年齡',
`card_id` int(11) NOT NULL DEFAULT '0' COMMENT '身份證 tb_idcard.id',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
CREATE TABLE `tb_idcard` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`card_no` varchar(18) NOT NULL COMMENT '身份證編號',
`address` varchar(200) NOT NULL DEFAULT '' COMMENT '地址',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
jdbc.properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/test
jdbc.username=root
jdbc.password=root
Mybatis配置文件保持不動 mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<properties resource="jdbc.properties"/>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mappers/UserMapper.xml"/>
</mappers>
</configuration>
User中添加IdCard的引用。
package cn.saytime.domain;
/**
* Created by L on 2018/1/3.
*/
public class User {
private Long id;
private String username;
private Integer age;
private IdCard idCard;
// getter/setter/toString
}
身份證IdCard
package cn.saytime.domain;
/**
* Created by L on 2018/1/3.
*/
public class IdCard {
private Long id;
// 身份證編號
private String cardId;
// 身份證地址
private String address;
// getter/setter/toString
}
UserMapper.java
/**
* 根據ID查詢用戶,並且查詢出身份證,使用嵌套查詢
* @return
*/
User selectByIdWithIdCard(Long id);
先說第一種嵌套查詢。
1. 嵌套查詢
<resultMap id="userWithIdCardMap" type="cn.saytime.domain.User">
<association property="idCard" column="card_id" javaType="cn.saytime.domain.IdCard" select="selectIdCard"/>
</resultMap>
<select id="selectByIdWithIdCard" resultMap="userWithIdCardMap" parameterType="java.lang.Long">
SELECT * FROM `test`.`tb_user` WHERE id = #{id}
</select>
<select id="selectIdCard" resultType="cn.saytime.domain.IdCard" parameterType="java.lang.Integer">
SELECT * FROM `test`.`tb_idcard` WHERE id = #{id}
</select>
測試:
import cn.saytime.domain.User;
import cn.saytime.mapper.UserMapper;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
/**
* Created by L on 2018/1/3.
*/
public class TestUserMapper {
SqlSessionFactory sqlSessionFactory = null;
SqlSession sqlSession = null;
UserMapper userMapper = null;
@Before
public void before(){
// mybatis 配置文件地址
String resource = "mybatis-config.xml";
InputStream inputStream = null;
try {
inputStream = Resources.getResourceAsStream(resource);
} catch (IOException e) {
e.printStackTrace();
}
// 加載配置文件,並構建sqlSessionFactory
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 獲取sqlSession對象
sqlSession = sqlSessionFactory.openSession();
userMapper = sqlSession.getMapper(UserMapper.class);
}
@After
public void after(){
if(sqlSession != null){
// 注意這裏的commit,否則提交不成功
sqlSession.commit();
sqlSession.close();
}
}
/**
* 測試根據Id查詢用戶並且帶上身份證信息
*/
@Test
public void testSelectByIdWithIdCard(){
User user = userMapper.selectByIdWithIdCard(2L);
System.out.println(user);
}
}
輸出,這裏的數據提前在數據庫準備好。
User{id=2, username='張三', age=18, idCard=IdCard{id=1, card_no='123456', address='廣東深圳'}}
我們有兩個查詢語句:一個來加載用戶信息,另外一個來加載身份證信息,而且身份證的結果映射描 述了“selectIdCard”語句應該被用來加載它的 idCard 屬性。
這種方式很簡單, 但是對於大型數據集合和列表將不會表現很好。 問題就是我們熟知的 “N+1 查詢問題”。概括地講,N+1 查詢問題可以是這樣引起的:
N+1問題
- 你執行了一個單獨的 SQL 語句來獲取結果列表(就是“+1”)。
- 對返回的每條記錄,你執行了一個查詢語句來爲每個加載細節(就是“N”)。
==嵌套查詢N+1解決辦法:延遲加載,但實際中,如果你加載一個列表,之後迅速迭代來訪問嵌套的數據,你會調用所有的延遲加載,這樣也會有性能問題==
<association fetchType="lazy">
<resultMap id="userWithIdCardMap" type="cn.saytime.domain.User">
<association property="idCard" column="card_id" javaType="cn.saytime.domain.IdCard" select="selectIdCard" fetchType="lazy"/>
</resultMap>
之後查詢用戶信息,如果沒有使用到IdCard的數據,是不會調用selectIdCard這個查詢的。
2. 嵌套結果
UserMapper.xml
<resultMap id="userWithIdCardMap2" type="cn.saytime.domain.User">
<id property="id" column="uid" javaType="java.lang.Long"></id>
<result property="username" column="username" javaType="java.lang.String"></result>
<result property="age" column="age" javaType="java.lang.Integer"></result>
<association property="idCard" javaType="cn.saytime.domain.IdCard">
<id property="id" column="cid" javaType="java.lang.Long"></id>
<result property="card_no" column="card_no" javaType="java.lang.String"></result>
<result property="address" column="address" javaType="java.lang.String"></result>
</association>
</resultMap>
<select id="selectByIdWithIdCard2" resultMap="userWithIdCardMap2" parameterType="java.lang.Long">
SELECT u.id AS uid, u.username, u.age, c.id AS cid, c.card_no, c.address
FROM `tb_user` u
JOIN `tb_idcard` c
WHERE u.id = #{id}
</select>
這裏注意別名,別衝突了
UserMapper.java
/**
* 根據ID查詢用戶,並且查詢出身份證,使用嵌套結果
* @return
*/
User selectByIdWithIdCard2(Long id);
測試:
/**
* 測試根據Id查詢用戶並且帶上身份證信息,使用嵌套結果
*/
@Test
public void testSelectByIdWithIdCard2(){
User user = userMapper.selectByIdWithIdCard2(2L);
System.out.println(user);
}
輸出:
User{id=2, username='張三', age=18, idCard=IdCard{id=1, card_no='123456', address='廣東深圳'}}