Mybatis(四):一對一級聯查詢

關於級聯查詢,會涉及到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='廣東深圳'}}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章