前言

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');

表關係分析

建立後的表為:

簡單來說,

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

2.引入依賴

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


<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://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"
"http://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>

(一)一對一查詢

第一種:關聯查詢

為了方便我直接在一個模組裡進行一對一和一對多關聯查詢,先看一下我的目錄結構,對要建立的相關檔案有一個瞭解,畫框框的為一對一查詢。,其餘的為一對多查詢。

目標

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

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"
"http://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屬性。這種情況下避免了資源浪費,在查詢某些欄位的值的時候不用每次都查詢兩張表。

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



可以對比一對一關聯查詢,分佈查詢多了介面的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"
"http://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"
"http://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"
"http://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"
"http://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"
"http://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"表示使用懶查詢,也就是分佈查詢。