三、MyBatis多表映射
1、多表映射的概念
1、思路
1、MyBatis 思想是:数据库不可能永远是你所想或所需的那个样子。 我们希望每个数据库都具备良好的第三范式或 BCNF 范式,可惜它们并不都是那样。 如果能有一种数据库映射模式,完美适配所有的应用程序查询需求,那就太好了,而 ResultMap 就是 MyBatis 就是完美答案。
2、图解

2、学习目标
-
多表查询语句使用
-
多表结果承接实体类设计
-
使用ResultMap完成多表结果映射
3、实体类设计方案
1、多表关系回顾
多表关系回顾:(双向查看)
-
一对一
夫妻关系,人和身份证号
-
一对多| 多对一
用户和用户的订单,锁和钥匙
-
多对多
老师和学生,部门和员工
2、实体类设计关系(查询):(单向查看)
1、对一
1、对一 : 夫妻一方对应另一方,订单对应用户都是对一关系
2、重点:实体类设计:对一关系下,类中只要包含单个对方对象类型属性即可!
3、例如:
public class Customer {
private Integer customerId;
private String customerName;
}
public class Order {
private Integer orderId;
private String orderName;
private Customer customer;// 体现的是对一的关系
}
2、对多
1、对多: 用户对应的订单,讲师对应的学生或者学生对应的讲师都是对多关系:
2、重点:实体类设计:对多关系下,类中只要包含对方类型集合属性即可!
3、例子
public class Customer {
private Integer customerId;
private String customerName;
private List<Order> orderList;// 体现的是对多的关系
}
public class Order {
private Integer orderId;
private String orderName;
private Customer customer;// 体现的是对一的关系
}
//查询客户和客户对应的订单集合 不要管!
3、多表结果实体类设计技巧
1、对一:属性包含对方对象
2、对多:属性包含对方对象集合
3、注意!
-
只有真实发生多表查询时,才需要设计和修改实体类,否则不提前设计和修改实体类!
-
无论多少张表联查,实体类设计都是两两考虑!
-
在查询映射的时候,只需要关注本次查询相关的属性!例如:查询订单和对应的客户,就不要关注客户中的订单集合!
2、对一映射
1、需求
1、根据ID查询订单,以及订单关联的用户的信息!
2、代码实现
1、准备数据库
CREATE TABLE `t_customer` (`customer_id` INT NOT NULL AUTO_INCREMENT, `customer_name` CHAR(100), PRIMARY KEY (`customer_id`) );
CREATE TABLE `t_order` ( `order_id` INT NOT NULL AUTO_INCREMENT, `order_name` CHAR(100), `customer_id` INT, PRIMARY KEY (`order_id`) );
INSERT INTO `t_customer` (`customer_name`) VALUES ('c01');
INSERT INTO `t_order` (`order_name`, `customer_id`) VALUES ('o1', '1');
INSERT INTO `t_order` (`order_name`, `customer_id`) VALUES ('o2', '1');
INSERT INTO `t_order` (`order_name`, `customer_id`) VALUES ('o3', '1');
SELECT order_id ,order_name,c.customer_id,customer_name
from t_order o
left JOIN t_customer c
ON o.customer_id =c.customer_id
where o.order_id = 1;
2、准备实体类
package com.atgui.pojo;
import lombok.Data;
@Data
public class Order {
private Integer orderId;
private String orderName;
private Integer customerId;
// 对一的关系,所以要保留customer的对象
Customer customer ;
}
package com.atgui.pojo;
import lombok.Data;
@Data
public class Customer {
private Integer customerId;
private String customerName;
}
3、准备配置文件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>
<!-- 让mybatis开启日志输出-->
<settings>
<!-- 这里设置的是选择用system进行日志输出-->
<setting name="logImpl" value="STDOUT_LOGGING"/>
<!-- 开启驼峰式自动映射-->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
<!-- 定义自己类的别名-->
<typeAliases>
<!-- 单独定义-->
<!-- <typeAlias type="com.atgui.pojo.Employee" alias="suibian"/>-->
<!-- 批量定义-->
<package name="com.atgui.pojo"/>
</typeAliases>
<!-- environments表示配置Mybatis的开发环境,可以配置多个环境,在众多具体环境中,使用default属性指定实际运行时使用的环境。default属性的取值是environment标签的id属性的值。 -->
<environments default="development">
<!-- environment表示配置Mybatis的一个具体的环境 -->
<environment id="development">
<!-- Mybatis的内置的事务管理器 -->
<transactionManager type="JDBC"/>
<!-- 配置数据源 -->
<dataSource type="POOLED">
<!-- 建立数据库连接的具体信息 -->
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis-example"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<mappers>
<!-- Mapper注册:指定Mybatis映射文件的具体位置 -->
<!-- mapper标签:配置一个具体的Mapper映射文件 -->
<!-- resource属性:指定Mapper映射文件的实际存储位置,这里需要使用一个以类路径根目录为基准的相对路径 -->
<!-- 对Maven工程的目录结构来说,resources目录下的内容会直接放入类路径,所以这里我们可以以resources目录为基准 -->
<mapper resource="mappers/OrderMapper.xml"/>
</mappers>
</configuration>
4、pom.xml和上面一样这里不多赘述
5、准备接口类
package com.atgui.mapper;
import com.atgui.pojo.Order;
public interface OrderMapper {
Order selectOrderWithCustomer(Integer orderId);
}
6、准备mappers/mapper.xml文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- namespace等于mapper接口类的全限定名,这样实现对应 -->
<mapper namespace="com.atgui.mapper.OrderMapper">
<resultMap id="orderMap" type="order">
<!-- 第一层属性,order对象-->
<id column="order_id" property="orderId"/>
<result column="order_name" property="orderName"/>
<result column="customer_id" property="customerId"/>
<!-- 第二层属性,customer对象-->
<association property="customer" javaType="customer">
<id column="customer_id" property="customerId"/>
<result column="customer_name" property="customerName"/>
</association>
</resultMap>
<select id="selectOrderWithCustomer" resultMap="orderMap">
SELECT order_id ,order_name,c.customer_id,customer_name
from t_order o
left JOIN t_customer c
ON o.customer_id =c.customer_id
where o.order_id = #{orderId};
</select>
</mapper>
7、测试
package com.atgui;
import com.atgui.mapper.UserMapper;
import com.atgui.pojo.User;
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.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
public class MybatisTest {
private SqlSession sqlSession;
// 每次测试方法执行前都会执行该代码
@BeforeEach
public void before() throws IOException {
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
sqlSession = sqlSessionFactory.openSession(true);
}
//每次测试方法执行后都会执行该代码
@AfterEach
public void clean(){
sqlSession.close();
}
@Test
public void testInsert(){
User user = new User();
user.setUsername("1231");
user.setPassword("123");
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
userMapper.insert(user);
System.out.println("userMapper = " + userMapper);
}
@Test
public void testUpdate(){
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = userMapper.selectById(1);//查询到这个再进行修改
user.setUsername("12311111");
user.setPassword("12322222");
userMapper.update(user);
}
@Test
public void testDelete(){
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
userMapper.delete(1);
}
@Test
public void testSelectById(){
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = userMapper.selectById(1);
System.out.println("user = " + user);
}
@Test
public void testSelectAll(){
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = userMapper.selectAll();
System.out.println("userList = " + userList);
}
}
3、关键字
1、在“对一”关联关系中,我们的配置比较多,但是关键词就只有:association和javaType
3、对多映射
1、需求
1、查询客户和客户订单关联的订单信息
2、代码实现
1、准备实体类
package com.atgui.pojo;
import lombok.Data;
import java.util.List;
@Data
public class Customer {
private Integer customerId;
private String customerName;
//对多的关系,就用一个集合装Order
private List<Order> orderList;
}
package com.atgui.pojo;
import lombok.Data;
@Data
public class Order {
private Integer orderId;
private String orderName;
private Integer customerId;
// 对一的关系,所以要保留customer的对象
Customer customer ;
}
3、准备配置文件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>
<!-- 让mybatis开启日志输出-->
<settings>
<!-- 这里设置的是选择用system进行日志输出-->
<setting name="logImpl" value="STDOUT_LOGGING"/>
<!-- 开启驼峰式自动映射-->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
<!-- 定义自己类的别名-->
<typeAliases>
<!-- 单独定义-->
<!-- <typeAlias type="com.atgui.pojo.Employee" alias="suibian"/>-->
<!-- 批量定义-->
<package name="com.atgui.pojo"/>
</typeAliases>
<!-- environments表示配置Mybatis的开发环境,可以配置多个环境,在众多具体环境中,使用default属性指定实际运行时使用的环境。default属性的取值是environment标签的id属性的值。 -->
<environments default="development">
<!-- environment表示配置Mybatis的一个具体的环境 -->
<environment id="development">
<!-- Mybatis的内置的事务管理器 -->
<transactionManager type="JDBC"/>
<!-- 配置数据源 -->
<dataSource type="POOLED">
<!-- 建立数据库连接的具体信息 -->
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis-example"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<mappers>
<!-- Mapper注册:指定Mybatis映射文件的具体位置 -->
<!-- mapper标签:配置一个具体的Mapper映射文件 -->
<!-- resource属性:指定Mapper映射文件的实际存储位置,这里需要使用一个以类路径根目录为基准的相对路径 -->
<!-- 对Maven工程的目录结构来说,resources目录下的内容会直接放入类路径,所以这里我们可以以resources目录为基准 -->
<mapper resource="mappers/OrderMapper.xml"/>
<mapper resource="mappers/CustomerMapper.xml"/>
</mappers>
</configuration>
4、pom.xml和上面一样这里不多赘述
5、准备接口类
package com.atgui.mapper;
import com.atgui.pojo.Customer;
import java.util.List;
public interface CustomerMapper {
List<Customer> queryList();
}
6、准备mappers/mapper.xml文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- namespace等于mapper接口类的全限定名,这样实现对应 -->
<mapper namespace="com.atgui.mapper.CustomerMapper">
<resultMap id="customerMap" type="customer">
<id column="customer_id" property="customerId"/>
<result column="customer_name" property="customerName"/>
<!-- 是给集合属性赋值
property 集合属性值
ofType 集合的泛型类型
-->
<collection property="orderList" ofType="order">
<id column="order_id" property="orderId"/>
<result column="order_name" property="orderName"/>
<result column="customer" property="customerId"/>
</collection>
</resultMap>
<select id="queryList" resultMap="customerMap">
SELECT order_id ,order_name,c.customer_id,customer_name
from t_order o
left JOIN t_customer c
ON o.customer_id =c.customer_id
</select>
</mapper>
7、测试
package com.atgui;
import com.atgui.mapper.CustomerMapper;
import com.atgui.mapper.OrderMapper;
import com.atgui.pojo.Customer;
import com.atgui.pojo.Order;
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.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
public class mybatisTest {
private SqlSession sqlSession;
// 每次测试方法执行前都会执行该代码
@BeforeEach
public void before() throws IOException {
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
sqlSession = sqlSessionFactory.openSession(true);
}
//每次测试方法执行后都会执行该代码
@AfterEach
public void clean(){
sqlSession.close();
}
@Test
public void test2(){
CustomerMapper customerMapper = sqlSession.getMapper(CustomerMapper.class);
List<Customer> customerList = customerMapper.queryList();
System.out.println("order = " + customerList);
}
}
3、理解和补充
1、对应关系如图

4、关键字
1、在“对多”关联关系中,同样有很多配置,但是提炼出来最关键的就是:“collection”和“ofType”
4、多表映射总结
1、多表映射优化
1、补充setting设置
| setting属性 | 属性含义 | 可选值 | 默认值 |
|---|---|---|---|
| autoMappingBehavior | 指定 MyBatis 应如何自动映射列到字段或属性。 NONE 表示关闭自动映射;PARTIAL 只会自动映射没有定义嵌套结果映射的字段。 FULL 会自动映射任何复杂的结果集(无论是否嵌套)。 | NONE, PARTIAL, FULL | PARTIAL |
2、根据上面的对多例子
-
①修改mybatis-config.xml的配置文件
-
②修改CustomerMapper.xml文件
2、总结
| 关联关系 | 配置项关键词 | 所在配置文件和具体位置 |
|---|---|---|
| 对一 | association标签/javaType属性/property属性 | Mapper配置文件中的resultMap标签内 |
| 对多 | collection标签/ofType属性/property属性 | Mapper配置文件中的resultMap标签内 |
3、补充【多表】
