Mybatis Plus 多表联查(包含分页关联查询,图文讲解)
一则或许对你有用的小广告
欢迎加入小哈的星球 ,你将获得:专属的项目实战 / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论
- 新项目:《从零手撸:仿小红书(微服务架构)》 正在持续爆肝中,基于
Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...
,点击查看项目介绍 - 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/
截止目前, 星球 内专栏累计输出 66w+ 字,讲解图 2896+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,权限管理,Spring Cloud Alibaba 微服务等等,已有 2300+ 小伙伴加入学习 ,欢迎点击围观
大家好,我是小哈。
本小节中,我们将学习如何通过 Mybatis Plus 实现多表关联查询,以及分页关联查询。
表结构
本文以查询用户所下订单,来演示 Mybatis Plus 的关联查询,数据库表除了前面小节中已经定义好的用户表外,再额外创建一张订单表,然后插入一些测试数据,执行脚本如下:
DROP TABLE IF EXISTS user;
CREATE TABLE `t_user` (
`id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`name` varchar(30) NOT NULL DEFAULT '' COMMENT '姓名',
`age` int(11) NULL DEFAULT NULL COMMENT '年龄',
`gender` tinyint(2) NOT NULL DEFAULT 0 COMMENT '性别,0:女 1:男',
PRIMARY KEY (`id`)
) COMMENT = '用户表';
INSERT INTO `t_user` (`id`, `name`, `age`, `gender`) VALUES (1, '犬小哈', 30, 1);
INSERT INTO `t_user` (`id`, `name`, `age`, `gender`) VALUES (2, '关羽', 46, 1);
INSERT INTO `t_user` (`id`, `name`, `age`, `gender`) VALUES (3, '诸葛亮', 26, 1);
CREATE TABLE `t_order` (
`id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`order_id` bigint(20) UNSIGNED NOT NULL COMMENT '订单ID',
`user_id` bigint(20) UNSIGNED NOT NULL COMMENT '下单用户ID',
`goods_name` varchar(30) NOT NULL COMMENT '商品名称',
`goods_price` decimal(10,2) NOT NULL COMMENT '商品价格',
PRIMARY KEY (`id`),
INDEX idx_order_id(`order_id`)
) COMMENT = '订单表';
INSERT INTO `t_order` (`id`, `order_id`, `user_id`, `goods_name`, `goods_price`) VALUES (1, 805646264648356, 1, 'Switch 游戏机', 1400.00);
INSERT INTO `t_order` (`id`, `order_id`, `user_id`, `goods_name`, `goods_price`) VALUES (2, 551787441310504, 1, '小米手机', 2000.00);
INSERT INTO `t_order` (`id`, `order_id`, `user_id`, `goods_name`, `goods_price`) VALUES (3, 938562101633493, 2, '《三国演义》', 66.00);
INSERT INTO `t_order` (`id`, `order_id`, `user_id`, `goods_name`, `goods_price`) VALUES (4, 791129917310894, 3, '华为手机', 1200.00);
INSERT INTO `t_order` (`id`, `order_id`, `user_id`, `goods_name`, `goods_price`) VALUES (5, 208722395587361, 3, '《西游记》', 56.00);
需求分析
假设前端需要展示数据有如下几个字段:订单号、商品名称、商品价格、下单用户姓名、下单用户年龄、下单用户性别:
则对应的关联 SQL 语句如下:
select o.order_id, o.goods_name, o.goods_price, u.name, u.age, u.gender
from t_order as o left join t_user as u on o.user_id = u.id
项目结构
先贴一张项目结构,下面所创建的类与文件可参考这里:
实体类
接下来,我们定义实体类。创建一个 OrderVO
视图类,用于传输给前端展示:
/**
* @author: 犬小哈
* @from: 公众号:小哈学Java, 网站:www.quanxiaoha.com
* @date: 2022-12-13 14:13
* @version: v1.0.0
* @description: TODO
**/
@Data
public class OrderVO {
/**
* 订单ID
*/
private Long orderId;
/**
* 下单用户ID
*/
private Long userId;
/**
* 商品名称
*/
private String goodsName;
/**
* 商品价格
*/
private BigDecimal goodsPrice;
/**
* 用户名
*/
private String userName;
/**
* 年龄
*/
private Integer userAge;
/**
* 性别
*/
private Integer userGender;
}
TIP:
@Data
是 Lombok 注解,偷懒用的,加上它即可免写繁杂的getXXX/setXXX
相关方法,不了解的小伙伴可自行搜索一下如何使用。
开始关联查询
简单的关联查询
创建 UserMapper
, 让其继承自 BaseMapper
, 并自定义一个查询订单列表的方法:
public interface UserMapper extends BaseMapper<User> {
// 查询订单列表
List<OrderVO> selectOrders();
}
在项目的 resource
目录下新建 mapper
文件夹,并在 mapper
文件夹中创建 UserMapper.xml
文件:
UserMapper.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="com.quanxiaoha.mybatisplusdemo.mapper.UserMapper">
<resultMap id="orderMap" type="com.quanxiaoha.mybatisplusdemo.model.OrderVO">
<result property="userName" column="name"/>
<result property="userAge" column="age"/>
<result property="userGender" column="gender"/>
<result property="orderId" column="order_id"/>
<result property="userId" column="user_id"/>
<result property="goodsName" column="goods_name"/>
<result property="goodsPrice" column="goods_price"/>
</resultMap>
<select id="selectOrders" resultMap="orderMap">
select o.order_id, o.user_id, o.goods_name, o.goods_price, u.name, u.age, u.gender
from t_order as o left join t_user as u on o.user_id = u.id
</select>
</mapper>
创建完了 UserMapper.xml
文件后,还需要在 applicatoin.yml
中添加如下配置,告诉 Mybatis Plus 框架去扫描这些 xml
文件:
mybatis-plus:
mapper-locations: classpath:/mapper/*Mapper.xml
然后,创建一个单元测试看看好不好使:
@Autowired
private UserMapper userMapper;
@Test
void testSelectOrders() {
List<OrderVO> orderVOS = userMapper.selectOrders();
}
执行上面的单元测试,实际执行 SQL 为:
select o.order_id, o.user_id, o.goods_name, o.goods_price, u.name, u.age, u.gender from t_order as o left join t_user as u on o.user_id = u.id
返回数据如下:
到这里,一个简单的关联查询就搞定了。
带分页的关联查询
实际开发场景中,很多关联查询都需要结合分页一起使用,假设上面展示的数据需要分页展示,且需要支持条件查询,要怎么做呢?
定义关联查询分页方法
在 UserMapper
接口中再定义支持分页的关联查询方法:
public interface UserMapper extends BaseMapper<User> {
//...
IPage<OrderVO> selectOrderPage(IPage<OrderVO> page, @Param(Constants.WRAPPER) QueryWrapper<OrderVO> wrapper);
//...
}
TIP : 可以看到我们定义的关联分页查询和 Myatis Plus 内部提供的分页方法相差不大,仔细看入参,我们复用了 Mybatis Plus 内部提供的分页类
IPage
,以及QueryWrapper
(用于组装where
条件)。
然后在 UserMapper.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="com.quanxiaoha.mybatisplusdemo.mapper.UserMapper">
<resultMap id="orderMap" type="com.quanxiaoha.mybatisplusdemo.model.OrderVO">
<result property="userName" column="name"/>
<result property="userAge" column="age"/>
<result property="userGender" column="gender"/>
<result property="orderId" column="order_id"/>
<result property="userId" column="user_id"/>
<result property="goodsName" column="goods_name"/>
<result property="goodsPrice" column="goods_price"/>
</resultMap>
//...
<select id="selectOrderPage" resultMap="orderMap">
select u.name, u.age, u.gender, o.order_id, o.goods_name, o.goods_price
from t_user as u left join t_order as o on u.id = o.user_id
${ew.customSqlSegment}
</select>
//...
</mapper>
再创建一个单元测试:
@Autowired
private UserMapper userMapper;
@Test
void testSelectOrdersPage() {
// 查询第一页,每页显示 10 条
Page<OrderVO> page = new Page<>(1, 10);
// 注意:一定要手动关闭 SQL 优化,不然查询总数的时候只会查询主表
page.setOptimizeCountSql(false);
// 组装查询条件 where age = 20
QueryWrapper<OrderVO> queryWrapper = new QueryWrapper<>();
queryWrapper.ge("age", 20);
IPage<OrderVO> page1 = userMapper.selectOrderPage(page, queryWrapper);
System.out.println("总记录数:" + page1.getTotal());
System.out.println("总共多少页:" + page1.getPages());
System.out.println("当前页码:" + page1.getCurrent());
System.out.println("查询数据:" + page1.getRecords());
}
执行该单元测试,控制台打印实际执行 SQL 如下,可见分页功能也是 OK 的,先执行 select count(*)
查询记录总数,然后再执行关联分页查询: