在我们日常开发中,经常会遇到查询一对多的业务场景,比如:订单,用户权限,用户菜单,文章评论,以前没有用到ORM的时候,都是批量取出再重新匹配或者循环取出,这在代码里面实现起来还是有些繁琐。在现今Mybatis、Hibernate等ORM框架里,都给我们提供方便的一对多的映射功能,使得我们的开发效率得到了提高。今天,我们就来演示一下订单一对多的关系映射。
我们先创建两张表,分别为td_order(订单表)和td_order_line(订单行表):
1
CREATE TABLE `mybatis`.`td_order` (
`id` int NOT NULL AUTO_INCREMENT,
`order_no` varchar(30) NULL,
`amount` decimal(10, 2) NULL,
`create_time` datetime NULL DEFAULT now(),
PRIMARY KEY (`id`)
);
CREATE TABLE `mybatis`.`td_order_line` (
`id` int NOT NULL AUTO_INCREMENT,
`order_id` int NULL,
`product_id` int NULL,
`product_name` varchar(100) NULL,
`order_number` int NULL,
`price` decimal(10, 2) NULL,
PRIMARY KEY (`id`)
);
INSERT td_order (order_no,amount) values ('202108310001','100');
INSERT td_order (order_no,amount) values ('202108310002','102');
INSERT td_order (order_no,amount) values ('202108310003','106');
insert td_order_line(order_id,product_id,product_name,order_number,price) VALUES(1,1,'商品1',2,35);
insert td_order_line(order_id,product_id,product_name,order_number,price) VALUES(1,2,'商品2',2,15);
insert td_order_line(order_id,product_id,product_name,order_number,price) VALUES(2,3,'商品3',3,15);
insert td_order_line(order_id,product_id,product_name,order_number,price) VALUES(2,4,'商品4',2,28.5);
insert td_order_line(order_id,product_id,product_name,order_number,price) VALUES(3,5,'商品5',4,15);
insert td_order_line(order_id,product_id,product_name,order_number,price) VALUES(3,6,'商品6',2,23);
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
该表结构仅为演示所用,并不规范。
接下来继续新建两个类:
然后新建一个OrderMapper接口,接下来就该创建一对多的映射关系了,这里主要介绍两种方式,第一种主映射和子映射在一个resultMap中:
还有另外一种就是主映射和子映射分为两个resultMap:
现在,我们开始编写SQL来把数据查询出来
为了方便后面做分页测试,我们就新增一个Controller来看看调用效果:


我从上面打印的查询结果可以看出,Mybatis把我们LEFT JOIN查询出来的数据进行了聚合分组,但是这样会有个弊端,就是在分页时计算出总记录数会有问题,什么问题,其实就是LEFT JOIN 关联出来的总记录数,但是其实我们只是想要主表的总记录数。我们来看看问题的现象吧


我们从SQL可以看得出,它是直接基于我们SQL来COUNT的,如果主表和从表是一对一的关系则不会出现这种问题,那么遇到一对多的分页该怎么办呢?接下来就是需要换种方式,采用主子表分别查询。直接看xml文件吧
这里需要注意的是,collection和子查询的sql中只能存在一个resultMap,否则会报错
collection里需要指定column来传递到子查询中,这里的id指的是主表查询结果中的id列。现在,我们来看看效果,


可以从上面的SQL看到,它是先查询主表的总记录数,取出主表当前分页的记录,再循环查询子表。
collection里指定column除了上面的单个字段,还可以指定多个字段如column="{id=id,year=year}",指定的列表在主表查询出来的结果必须存在。