三、mybatis多表關聯查詢和分佈查詢

前言

mybatis多表關聯查詢和懶查詢,這篇文章通過一對一和一對多的實例來展示多表查詢。不過需要掌握數據輸出的這方面的知識。之前整理過了mybatis入門案例mybatis數據輸出,多表查詢是在前面的基礎上完成的。如果不熟練的先回去鞏固一下。

準備工作

這裡先將兩個查詢要完成的共同步驟先完成

1.物理建模

創建兩個表,一個customer表,一個order表。

CREATE TABLE `t_customer` (
     `customer_id` INT NOT NULL AUTO_INCREMENT, 
     `customer_name` VARCHAR(100), 
     PRIMARY KEY (`customer_id`) 
);
CREATE TABLE `t_order` ( 
    `order_id` INT NOT NULL AUTO_INCREMENT, 
    `order_name` VARCHAR(100), 
    `customer_id` INT, 
    PRIMARY KEY (`order_id`) 
); 
INSERT INTO `t_customer` (`customer_name`) VALUES ('tom');
INSERT INTO `t_order` (`order_name`, `customer_id`) VALUES ('筆記本', '1'); 
INSERT INTO `t_order` (`order_name`, `customer_id`) VALUES ('電腦', '1'); 
INSERT INTO `t_order` (`order_name`, `customer_id`) VALUES ('桌子', '1');

表關係分析
創建後的表為:

表關係.jpg

簡單來說,

  • 一個顧客可以有多個訂單,所以t_customer表和t_order表示一對多關係
  • 一個訂單對應一個客戶或者多個訂單對應一個客戶,所以t_order表和t_customer表可以看成一對一或者多對一關係

2.引入依賴

在pom.xml中引入相關依賴,並將log4j的配置文件複製到resources路徑下。


<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="//maven.apache.org/POM/4.0.0"
         xmlns:xsi="//www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="//maven.apache.org/POM/4.0.0 //maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>day02-mybatis02</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.8</version>
            <scope>provided</scope>
        </dependency>

        <!-- Mybatis核心 -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.7</version>
        </dependency>

        <!-- junit測試 -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>

        <!-- MySQL驅動 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.3</version>
            <scope>runtime</scope>
        </dependency>

        <!-- log4j日誌 -->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
    </dependencies>

</project>

3.全局配置文件

這裡設置駝峰映射,別名配置,環境配置和路徑映射,別名和路徑用的是包掃描,因此在映射配置文件中做相應的修改即可。


<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "//mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!--駝峰映射-->
    <settings>
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>

    <!--類型別名配置-->
    <typeAliases>
       <!-- <typeAlias type="pojo.Employee" alias="employee"></typeAlias>-->
        <!--
            採用包掃描的方式,一次性對某個包中的所有類配置別名,每個類的別名就是它的類名,不區分大小寫
        -->
        <package name="pojo"/>
    </typeAliases>

    <environments default="dev">
        <environment id="dev">
            <transactionManager type="JDBC"></transactionManager>
            <dataSource type="POOLED">
                <!--
               dataSource:數據源
                   1. POOLED 表示使用內置連接池
                   2. UNPOOLED 表示不使用連接池
                   3. JNDI
           -->
                <property name="username" value="root"></property>
                <property name="password" value="888888"></property>
                <property name="driver" value="com.mysql.jdbc.Driver"></property>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis-example"></property>
            </dataSource>

        </environment>
    </environments>

    <mappers>
        <!--resource=映射路徑-->
       <!-- <mapper resource="mappers/EmployeeMapper.xml"/>-->
        <package name="mappers"/>
    </mappers>


</configuration>

(一)一對一查詢

第一種:關聯查詢

為了方便我直接在一個模塊里進行一對一和一對多關聯查詢,先看一下我的目錄結構,對要創建的相關文件有一個了解,畫框框的為一對一查詢。,其餘的為一對多查詢。
一對一.jpg

目標

根據訂單ID查詢出訂單信息,並且查詢出該訂單所屬的顧客信息,將查詢到的結果集封裝到Order對象中,所以要有一個order和customer類,將客戶信息轉成customer對象,然後封裝到Order對象中。

一對一查詢結果.jpg

1、邏輯建模

在pojo類下建order和customer,要注意的是,因為我們的目標是要根據訂單Id查詢出訂單信息和顧客信息,而訂單信息中有一個cutomer_id是和顧客表關聯的,查詢出來的是一條信息,所以我們在order類中要聲明屬性customer,將客戶信息轉成customer對象,封裝到訂單中。

package pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Customer {
    private Integer customerId;
    private String customerName;
}
package pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Order {

        private Integer orderId;
        private String orderName;
        //表示Order和Customer的對一關係
        private Customer customer;

}

2、創建持久層接口

因為在全局文件中配置的映射路徑是包掃描<package name="mappers"/>,所以持久層接口在建在mappers包下

package mappers;

import pojo.Order;

public interface OrderMapper {
    /*根據orderId查詢訂單信息並且查詢該訂單的顧客信息查詢出來,結果集封裝到Order對象中*/
    Order selectOrderWithCustomerByOrderId(Integer orderId);

}

3、創建映射配置文件

映射配置文件名字和在resources下的位置要與接口一致。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "//mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="mappers.OrderMapper">
    <!--
    手動映射,
    autoMapping屬性:對於可以自動映射的字段進行自動映射
    -->
    <resultMap id="OrderMap" type="Order" autoMapping="true">
        <!--
         association標籤進行一對一映射,類型是javaType
                property屬性:表示要對POJO的哪個屬性進行一對一映射
                javaType屬性:表示要進行一對一映射的那個屬性的類型(全限定名)
                -->
        <association property="customer" javaType="Customer" autoMapping="true"></association>
    </resultMap>

    <select id="selectOrderWithCustomerByOrderId" resultMap="OrderMap">
        select * from t_order `to`,t_customer tc where `to`.customer_id=tc.customer_id and `to`.order_id=#{orderId}
    </select>
</mapper>

這裡有三點需要注意:

  • ①多表查詢都需要用到手動映射,之前的數據輸出是resulType,手動映射的數據輸出是resultMap,在這裡設置autoMapping=ture,表示能自動映射的自動映射,就不必要寫id,result屬性。
  • ②在手動映射中,association標籤進行一對一映射,類型是javaType,javaType寫要封裝的類型(這裡注意要和一對多查詢區分開)
  • ③select標籤,要引用前面寫的手動映射,準備sql語句。

4、測試程序

import mappers.OrderMapper;
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.InputStream;

public class Test1v1 {
    private OrderMapper orderMapper ;
    private InputStream is;
    private SqlSession sqlSession;
    @Before
    public void init() throws Exception{
        //目標:獲取EmployeeMapper接口的代理對象,並且使用該對象調用selectEmployee(1)方法,然後返回Employee對象
        //1. 將全局配置文件轉成位元組輸入流
        is = Resources.getResourceAsStream("mybatis-config.xml");
        //2. 創建SqlSessionFactoryBuilder對象
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        //3. 使用構建者模式創建SqlSessionFactory對象
        SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
        //4. 使用工廠模式創建一個SqlSession對象
        sqlSession = sqlSessionFactory.openSession();
        //5. 使用動態代理模式,創建EmployeeMapper接口的代理對象
        orderMapper = sqlSession.getMapper(OrderMapper.class);
    }


    @After
    public void after() throws Exception{
        //提交事務!!!
        sqlSession.commit();
        //7. 關閉資源
        is.close();
        sqlSession.close();
    }

    @Test
    public void testSelectOrderWithCustomerByOrderId(){

        System.out.println(orderMapper.selectOrderWithCustomerByOrderId(2));
    }
}

第二種:分佈查詢(懶查詢)

分佈查詢則需要查詢分佈兩張表,將第二步查詢到的結果賦值給Order對象的customer屬性。這種情況下避免了資源浪費,在查詢某些字段的值的時候不用每次都查詢兩張表。

我這裡還是將一對一和一對多查詢放在一個模塊下,但是建議分開放,思路相對會清晰一點,框住的還是一對一查詢需要創建的表。

分佈查詢.jpg
可以對比一對一關聯查詢,分佈查詢多了接口的CustomerMapper以及映射文件的CustomerMapper.xml文件。

目標

  • 第一步:根據order_id查詢出訂單信息,得到customer_id
  • 第二步:根據customer_id查詢出顧客信息
  • 第三步:將第二步查詢到的結果賦值給Order對象的customer屬性

1.邏輯建模

和一對一關聯查詢一樣

2.創建持久性接口

  • OrderMapper接口,根據order_id查詢出訂單信息:
public interface OrderMapper {
    Order selectOrderByOrderId(Integer orderId);

}
  • CustomerMapper接口,根據customer_id查詢出顧客信息:
public interface CustomerMapper {
    Customer selectCustomerByCustomerId(Integer customerId);
}

3.創建映射配置文件

OrderMapper.xml(第一步+第三步):

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "//mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="mapper.OrderMapper">
    <resultMap id="OrderWithCustomerMap" type="order" autoMapping="true">
        <!--
            將第二步查詢到的結果賦值給Order對象的customer屬性
            select屬性:表示調用第二步查詢,獲取查詢結果
            column屬性:表示將本次查詢到的結果集中的哪個字段傳給第二步查詢
        -->
        <association property="customer" javaType="Customer" 
        autoMapping="true"  column="customer_id"
        select="mapper.CustomerMapper.selectCustomerByCustomerId" 
         fetchType="lazy"
         ></association>
    </resultMap>
    
    <!--第一步查詢-->
    <select id="selectOrderByOrderId" resultMap="OrderWithCustomerMap">
        SELECT * FROM t_order WHERE order_id =#{orderId};
    </select>
</mapper>

CustomerMapper.xml(第二步):

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "//mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="mapper.CustomerMapper">
    <select id="selectCustomerByCustomerId" resultType="customer">
        SELECT * FROM t_customer WHERE customer_id=#{customerId};
    </select>
</mapper>

說明:

  1. ①第二步只需要查詢根據customer_id查詢出顧客信息,我們重點放在如何在OrderMapper.xml中將第二步查詢到的信息封裝給Order的customer屬性。
  2. ②OrderMapper.xml中:我們知道,只要涉及多表查詢,我們都必須設置手動映射,而一對一的手動映射是association
  • select屬性:表示調用第二步查詢,獲取查詢結果 ,要寫第二步的全限定名
  • column屬性:表示將本次查詢到的結果集中的哪個字段傳給第二步查詢,根據customer_id查詢顧客信息。
  • fetchType=”lazy”表示使用懶查詢,也就是分佈查詢。

4.測試程序

import mapper.OrderMapper;
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.InputStream;

public class Test1v1 {
    private OrderMapper orderMapper ;
    private InputStream is;
    private SqlSession sqlSession;
    @Before
    public void init() throws Exception{
        //目標:獲取EmployeeMapper接口的代理對象,並且使用該對象調用selectEmployee(1)方法,然後返回Employee對象
        //1. 將全局配置文件轉成位元組輸入流
        is = Resources.getResourceAsStream("mybatisConfig.xml");
        //2. 創建SqlSessionFactoryBuilder對象
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        //3. 使用構建者模式創建SqlSessionFactory對象
        SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
        //4. 使用工廠模式創建一個SqlSession對象
        sqlSession = sqlSessionFactory.openSession();
        //5. 使用動態代理模式,創建EmployeeMapper接口的代理對象
        orderMapper = sqlSession.getMapper(OrderMapper.class);
    }


    @After
    public void after() throws Exception{
        //提交事務!!!
        sqlSession.commit();
        //7. 關閉資源
        is.close();
        sqlSession.close();
    }

    @Test
    public void testSelectOrderByOrderId(){
        System.out.println(orderMapper.selectOrderByOrderId(1));
    }
}

(二)一對多查詢

第一種 關聯查詢

目標

根據顧客id查詢顧客信息和訂單信息

1.邏輯建模

一個顧客對應多個訂單,查詢出來的有多條數據。所以需要在顧客中聲明一個泛型為Order的集合:

  • Customer1
package pojo;

import java.util.List;

public class Customer1 {
    private Integer customerId;
    private String customerName;
    private List<Order1> orderList;

    @Override
    public String toString() {
        return "Customer1{" +
                "customerId=" + customerId +
                ", customerName='" + customerName + ''' +
                ", orderList=" + orderList +
                '}';
    }
}
  • Order1
package pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Order1 {
    private Integer orderId;
    private String orderName;
}

2. 創建持久性接口

package mappers;

import pojo.Customer1;

public interface CustomerMapper {
    Customer1 selectCustomerWithOrderList(Integer customerId);
}

3. 編寫配置文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "//mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="mappers.CustomerMapper">

    <!--
            一對多映射:collection標籤
                property屬性表示要對POJO的哪個屬性進行一對多映射
                ofType屬性表示POJO中要進行一對多映射的那個屬性的泛型的全限定名
        -->
    <resultMap id="customerMap" type="Customer1" autoMapping="true">
    <!--這裡不能省略-->
        <id column="customer_id" property="customerId"></id>
        <result column="customer_name" property="customerName"></result>

        <collection property="orderList" ofType="Order1" autoMapping="true"></collection>
    </resultMap>

    <select id="selectCustomerWithOrderList" resultMap="customerMap">
        select * from t_order `to`,t_customer tc where `to`.customer_id=tc.customer_id and tc.customer_id=#{customerId}
    </select>
</mapper>

注意:

  • ①在手動映射中,collection標籤進行一對多映射一對一是association,javaType
  1. ofType**,表示POJO中要進行一對多映射的那個屬性的泛型的全限定名
  2. property屬性表示要對POJO的哪個屬性進行一對多映射。
  • ②這裡和一對一查詢不同還有:手動映射中的id是不可以省略的,
    因為我們查詢時結果有多行,自動映射先看方法的返回值,返回值是Customer1,底層會調用一個對象方法selectOne,多條數據底層調用的是selectList方法,根據接口的方法可以看到返回值是一個對象,只有一條數據,調用selectOne方法,而我們查詢出來的有多條,所以手動映射的id,result一定要加上。
    但是注意:如果我們沒有設置自動映射的情況下,result屬性一定要寫,不然只能打印一個空值。

4.測試程序

import mappers.CustomerMapper;
import mappers.OrderMapper;
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.InputStream;

public class Test1vn {
    private CustomerMapper customerMapper ;
    private InputStream is;
    private SqlSession sqlSession;
    @Before
    public void init() throws Exception{
        //目標:獲取EmployeeMapper接口的代理對象,並且使用該對象調用selectEmployee(1)方法,然後返回Employee對象
        //1. 將全局配置文件轉成位元組輸入流
        is = Resources.getResourceAsStream("mybatis-config.xml");
        //2. 創建SqlSessionFactoryBuilder對象
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        //3. 使用構建者模式創建SqlSessionFactory對象
        SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
        //4. 使用工廠模式創建一個SqlSession對象
        sqlSession = sqlSessionFactory.openSession();
        //5. 使用動態代理模式,創建EmployeeMapper接口的代理對象
        customerMapper = sqlSession.getMapper(CustomerMapper.class);
    }


    @After
    public void after() throws Exception{
        //提交事務!!!
        sqlSession.commit();
        //7. 關閉資源
        is.close();
        sqlSession.close();
    }

    @Test
    public void testSelectCustomerWithOrderList(){
        System.out.println(customerMapper.selectCustomerWithOrderList(1));

    }
}

第二種 分佈查詢(懶查詢)

目標

  • 第一步:根據customer_id查詢出顧客信息
  • 第二步:根據customer_id查詢出訂單信息
  • 第三步:將查詢到的訂單信息封裝到orderList集合中

1.邏輯建模

和一對多關聯查詢一樣

2.創建持久性接口

CustomerMapper1接口:

package mapper;

import pojo.Customer;
import pojo.Customer1;

public interface CustomerMapper1 {
    Customer1 selectCustomer1ByCustomerId(Integer customerId);
}

OrderMapper1 接口:

package mapper;

import pojo.Order1;

import java.util.List;

public interface OrderMapper1 {
    List<Order1> selectOrder1ByOrderId(Integer OrderId);
}

3. 映射配置文件

CustomerMapper1.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "//mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="mapper.CustomerMapper1">
    <resultMap id="customerWithOrderMap" type="customer1" autoMapping="true">
        <id column="customer_id" property="customerId" ></id>
        <collection property="orderList1"   ofType="Order1"
                    select="mapper.OrderMapper1.selectOrder1ByOrderId"
                    column="customer_id" fetchType="lazy"></collection>

    </resultMap>
    <select id="selectCustomer1ByCustomerId" resultMap="customerWithOrderMap">
        SELECT * FROM t_customer WHERE customer_id = #{customerId};
    </select>
</mapper>

OrderMapper1.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
       PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
       "//mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="mapper.OrderMapper1">

   <select id="selectOrder1ByOrderId" resultType="order1">
       SELECT * FROM t_order WHERE customer_id = #{customerId};
   </select>
</mapper>

注意:

  1. ①第二步只需要查詢根據customer_id查詢出訂單信息,其餘同一對多的關聯查詢
  2. ②CustomerMapper.xml中:一對多的手動映射是collection
  • select屬性:表示調用第二步查詢,獲取查詢結果 ,要寫第二步的全限定名
  • column屬性:表示將本次查詢到的結果集中的哪個字段傳給第二步查詢,根據customer_id查詢訂單信息。
  • fetchType=”lazy”表示使用懶查詢,也就是分佈查詢。