学科分类
目录
SSM框架

多对多

在实际项目开发中,多对多的关联关系也是非常常见的。以订单和商品为例,一个订单可以包含多种商品,而一种商品又可以属于多个订单,订单和商品就属于多对多的关联关系,如图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即可查询出订单及其关联的商品信息。

点击此处
隐藏目录