多对多
在实际项目开发中,多对多的关联关系也是非常常见的。以订单和商品为例,一个订单可以包含多种商品,而一种商品又可以属于多个订单,订单和商品就属于多对多的关联关系,如图1所示。
图1 订单和商品之间的关联关系
在数据库中,多对多的关联关系通常使用一个中间表来维护,中间表中的订单id作为外键参照订单表的id,商品id作为外键参照商品表的id。这三个表之间的关系如图2所示。
图2 数据库中订单表、中间表与商品表之间的关联
了解了数据库中订单表与商品表之间的多对多关联关系后,下面我们就通过具体的案例来讲解下如何使用MyBatis来处理这种多对多的关系,具体实现步骤如下。
(1)创建数据表。在mybatis数据库中新建名称为tb_product和tb_ordersitem 的2个数据表,同时在表中预先插入几条数据。其执行的SQL语句如下所示:
# 创建一个名称为tb_product的表
CREATE TABLE tb_product (
id INT(32) PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(32),
price DOUBLE
);
# 插入3条数据
INSERT INTO tb_product VALUES ('1', 'Java基础入门', '44.5');
INSERT INTO tb_product VALUES ('2', 'Java Web程序开发入门', '38.5');
INSERT INTO tb_product VALUES ('3', 'SSM框架整合实战', '50');
# 创建一个名称为tb_ordersitem 的中间表
CREATE TABLE tb_ordersitem (
id INT(32) PRIMARY KEY AUTO_INCREMENT,
orders_id INT(32),
product_id INT(32),
FOREIGN KEY(orders_id) REFERENCES tb_orders(id),
FOREIGN KEY(product_id) REFERENCES tb_product(id)
);
# 插入3条数据
INSERT INTO tb_ordersitem VALUES ('1', '1', '1');
INSERT INTO tb_ordersitem VALUES ('2', '1', '3');
INSERT INTO tb_ordersitem VALUES ('3', '3', '3');
由于订单表在上一小节中已经创建,所以这里只创建了商品表和中间表。完成上述操作后,tb_product表和tb_ordersitem 表中的数据如图3所示。
图3 tb_product和tb_ordersitem 表
(2)在com.itheima.po包中,创建持久化类Product,并在类中定义相关属性和方法,如文件1所示。
文件1 Product.java
1 package com.itheima.po;
2 import java.util.List;
3 /**
4 * 商品持久化类
5 */
6 public class Product {
7 private Integer id; //商品id
8 private String name; //商品名称
9 private Double price;//商品单价
10 private List<Orders> orders; //与订单的关联属性
11 public Integer getId() {
12 return id;
13 }
14 public void setId(Integer id) {
15 this.id = id;
16 }
17 public String getName() {
18 return name;
19 }
20 public void setName(String name) {
21 this.name = name;
22 }
23 public Double getPrice() {
24 return price;
25 }
26 public void setPrice(Double price) {
27 this.price = price;
28 }
29 public List<Orders> getOrders() {
30 return orders;
31 }
32 public void setOrders(List<Orders> orders) {
33 this.orders = orders;
34 }
35 @Override
36 public String toString() {
37 return "Product [id=" + id + ", name=" + name
38 + ", price=" + price + "]";
39 }
40 }
除了在商品持久化类中需要添加订单的集合属性外,还需要在订单持久化类(Orders.java)中增加商品集合的属性及其对应的getter/setter方法,同时为了方便查看输出结果,需要重写toString()方法,Orders类中添加的代码如下所示。
//关联商品集合信息
private List<Product> productList;
//省略getter/setter方法,以及重写的toString()方法
(3)在com.itheima.mapper包中,创建订单实体映射文件OrdersMapper.xml和商品实体映射文件ProductMapper.xml,对两个映射文件进行编辑后,如文件2和3所示。
文件2 OrdersMapper.xml
1 <?xml version="1.0" encoding="UTF-8"?>
2 <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
3 "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
4 <mapper namespace="com.itheima.mapper.OrdersMapper">
5 <!-- 多对多嵌套查询:通过执行另外一条SQL映射语句来返回预期的特殊类型 -->
6 <select id="findOrdersWithPorduct" parameterType="Integer"
7 resultMap="OrdersWithProductResult">
8 select * from tb_orders WHERE id=#{id}
9 </select>
10 <resultMap type="Orders" id="OrdersWithProductResult">
11 <id property="id" column="id" />
12 <result property="number" column="number" />
13 <collection property="productList" column="id" ofType="Product"
14 select="com.itheima.mapper.ProductMapper.findProductById">
15 </collection>
16 </resultMap>
17 </mapper>
在文件2中,使用嵌套查询的方式定义了一个id为findOrdersWithPorduct的select语句来查询订单及其关联的商品信息。在<resultMap>元素中使用了<collection>元素来映射多对多的关联关系,其中property属性表示订单持久化类中的商品属性,ofType属性表示集合中的数据为Product类型,而column的属性值会作为参数执行ProductMapper中定义的id为findProductById的执行语句来查询订单中的商品信息。
文件3 ProductMapper.xml
1 <?xml version="1.0" encoding="UTF-8"?>
2 <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
3 "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
4 <mapper namespace="com.itheima.mapper.ProductMapper">
5 <select id="findProductById" parameterType="Integer"
6 resultType="Product">
7 SELECT * from tb_product where id IN(
8 SELECT product_id FROM tb_ordersitem WHERE orders_id = #{id}
9 )
10 </select>
11 </mapper>
在文件3中,定义了一个id为findProductById的执行语句,该执行语句中的SQL会根据订单id查询与该订单所关联的商品信息。由于订单和商品是多对多的关联关系,所以需要通过中间表来查询商品信息。
(4)将新创建的映射文件OrdersMapper.xml和ProductMapper.xml的文件路径配置到核心配置文件mybatis-config.xml中,代码如下所示。
<mapper resource="com/itheima/mapper/OrdersMapper.xml" />
<mapper resource="com/itheima/mapper/ProductMapper.xml" />
(5)在测试类MybatisAssociatedTest中,编写多对多关联查询的测试方法findOrdersTest(),其代码如下所示。
/**
* 多对多
*/
@Test
public void findOrdersTest(){
// 1、通过工具类生成SqlSession对象
SqlSession session = MybatisUtils.getSession();
// 2、查询id为1的订单中的商品信息
Orders orders = session.selectOne("com.itheima.mapper."
+ "OrdersMapper.findOrdersWithPorduct", 1);
// 3、输出查询结果信息
System.out.println(orders);
// 4、关闭SqlSession
session.close();
}
使用JUnit4执行findOrdersTest()方法后,控制台的输出结果如图4所示。
图4 运行结果
从图4可以看出,使用MyBatis嵌套查询的方式执行了两条SQL语句,并查询出了订单及其关联的商品信息,这就是MyBatis多对多的关联查询。
如果读者对多表关联查询的SQL语句比较熟的话,也可以在OrdersMapper.xml中使用嵌套结果的方式,其代码如下所示。
<!-- 多对多嵌套结果查询:查询某订单及其关联的商品详情 -->
<select id="findOrdersWithPorduct2" parameterType="Integer"
resultMap="OrdersWithPorductResult2">
select o.*,p.id as pid,p.name,p.price
from tb_orders o,tb_product p,tb_ordersitem oi
WHERE oi.orders_id=o.id
and oi.product_id=p.id
and o.id=#{id}
</select>
<!-- 自定义手动映射类型 -->
<resultMap type="Orders" id="OrdersWithPorductResult2">
<id property="id" column="id" />
<result property="number" column="number" />
<!-- 多对多关联映射:collection -->
<collection property="productList" ofType="Product">
<id property="id" column="pid" />
<result property="name" column="name" />
<result property="price" column="price" />
</collection>
</resultMap>
在上述执行代码中,只定义了一条查询SQL,通过该SQL即可查询出订单及其关联的商品信息。