三、mybatis多表关联查询和分布查询

时间:2022-09-19 15:09:53

前言

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

表关系分析

创建后的表为:

三、mybatis多表关联查询和分布查询

简单来说,

  • 一个顾客可以有多个订单,所以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>

(一)一对一查询

第一种:关联查询

为了方便我直接在一个模块里进行一对一和一对多关联查询,先看一下我的目录结构,对要创建的相关文件有一个了解,画框框的为一对一查询。,其余的为一对多查询。

三、mybatis多表关联查询和分布查询

目标

根据订单ID查询出订单信息,并且查询出该订单所属的顾客信息,将查询到的结果集封装到Order对象中,所以要有一个order和customer类,将客户信息转成customer对象,然后封装到Order对象中。

三、mybatis多表关联查询和分布查询

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属性。这种情况下避免了资源浪费,在查询某些字段的值的时候不用每次都查询两张表。

我这里还是将一对一和一对多查询放在一个模块下,但是建议分开放,思路相对会清晰一点,框住的还是一对一查询需要创建的表。

三、mybatis多表关联查询和分布查询

可以对比一对一关联查询,分布查询多了接口的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"表示使用懒查询,也就是分布查询。