Markdown|SpringBoot MyBatis-Plus 多表联查分页实战
【摘要】 MyBatis-Plus 简化了单表CRUD开发,但很多新手在多表关联分页查询时容易踩坑。本文结合实战场景,讲解不写复杂XML、使用MP分页插件实现多表联查分页,附带完整代码、配置和测试案例,同时对比几种多表查询方案的优缺点,适合后端开发快速落地业务列表查询功能
前言
MyBatis-Plus(简称 MP)作为国内常用的MyBatis增强工具,极大减少了单表增删改查的样板代码。但日常业务中,几乎所有列表接口都需要多表关联查询,例如订单表关联用户表、商品表,同时还要支持分页、条件筛选。
很多同学有两个误区:
- 认为MP只能做单表,多表必须手写大量XML;
- 直接使用MP分页插件嵌套join语句,忽略分页插件原理,导致总数统计异常。
本文采用自定义Mapper方法 + MP分页插件的方式实现多表分页,兼顾开发效率与性能,代码可直接复制到项目运行。
环境依赖
SpringBoot 2.7.x + MyBatis-Plus 3.5.3.1 + MySQL8.0
<!-- pom.xml -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
场景说明
业务场景:订单列表查询,需要查询订单主表t_order,关联用户表t_user获取用户名,支持分页、订单号模糊查询。
- t_order:id, order_no, user_id, amount, create_time
- t_user:id, username, phone
1. 分页插件配置(必须配置)
MP分页插件是实现分页的核心,不配置会导致分页参数不生效,total总数为0
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MyBatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// MySQL分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return interceptor;
}
}
2. 实体类
订单实体
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@TableName("t_order")
public class Order {
private Long id;
private String orderNo;
private Long userId;
private Integer amount;
private LocalDateTime createTime;
}
用户实体
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
@Data
@TableName("t_user")
public class User {
private Long id;
private String username;
private String phone;
}
3. 自定义返回DTO(多表查询返回对象)
不要直接使用数据库实体返回,使用DTO接收多表查询字段,符合分层开发规范
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class OrderUserDTO {
private Long id;
private String orderNo;
private Long userId;
private String username;
private Integer amount;
private LocalDateTime createTime;
}
4. Mapper接口
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.ibatis.annotations.Param;
import java.util.Map;
public interface OrderMapper extends com.baomidou.mybatisplus.core.mapper.BaseMapper<Order> {
// 多表联查分页
IPage<OrderUserDTO> selectOrderWithUserPage(Page<OrderUserDTO> page, @Param("query") Map<String,Object> query);
}
5. Mapper.xml 编写SQL
resources/mapper/OrderMapper.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](http://mybatis.org/dtd/mybatis-3-mapper.dtd)">
<mapper namespace="com.demo.mapper.OrderMapper">
<select id="selectOrderWithUserPage" resultType="com.demo.dto.OrderUserDTO">
SELECT o.id, o.order_no, o.user_id, u.username, o.amount, o.create_time
FROM t_order o
LEFT JOIN t_user u ON o.user_id = u.id
<where>
<if test="query.orderNo != null">
o.order_no LIKE CONCAT('%',#{query.orderNo},'%')
</if>
</where>
ORDER BY o.create_time DESC
</select>
</mapper>
重点:这里只需要写主体查询SQL,分页插件会自动拼接limit,自动执行count查询,无需手动写count语句。
6. Service层调用示例
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;
@Service
public class OrderService {
@Resource
private OrderMapper orderMapper;
public IPage<OrderUserDTO> getOrderPage(long pageNum, long pageSize, String orderNo){
Page<OrderUserDTO> page = new Page<>(pageNum, pageSize);
Map<String,Object> query = new HashMap<>();
query.put("orderNo", orderNo);
return orderMapper.selectOrderWithUserPage(page, query);
}
}
7. Controller测试接口
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
@RequestMapping("/order")
public class OrderController {
@Resource
private OrderService orderService;
@GetMapping("/page")
public IPage<OrderUserDTO> page(
@RequestParam(defaultValue = "1") long pageNum,
@RequestParam(defaultValue = "10") long pageSize,
@RequestParam(required = false) String orderNo
){
return orderService.getOrderPage(pageNum, pageSize, orderNo);
}
}
8. 踩坑点总结
- 分页插件一定要注册Bean,否则分页不生效;
- 多表联查尽量使用
LEFT JOIN,避免主表数据丢失; - 多表查询返回不要用Entity,推荐DTO;
- 复杂SQL不要强行用MP的Wrapper,自定义XML可读性更好;
- 大数据量场景,count查询会有性能问题,可以自定义count语句优化。
9. 方案对比
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| MP Wrapper 单表 + 循环查关联数据 | 不用写XML | N+1查询问题,性能差 | 小数据量简单关联 |
| 自定义XML + MP分页插件 | 性能可控,灵活 | 少量XML | 绝大多数业务列表 |
| Join + 手写分页SQL | 完全可控 | 需要手动维护分页、count | 超大数据量 |
结语
MyBatis-Plus 不是替代MyBatis,而是增强工具。单表CRUD交给MP简化开发,多表复杂查询交给原生MyBatis XML,二者结合才是最佳实践。本文的多表分页方案在企业项目中广泛使用,上手简单,同时规避N+1等常见性能坑。实际开发中还可以结合参数校验、接口返回统一封装,进一步完善接口。
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)