当前位置: 首页 > news >正文

SpringBoot项目实战:用MyBatis-Plus-Join搞定多表联查(附完整代码)

SpringBoot实战:MyBatis-Plus-Join实现高效多表联查全攻略

在业务系统开发中,多表联查是绕不开的刚需场景。传统MyBatis需要手动编写复杂SQL,而MyBatis-Plus-Join(简称MPJ)的出现,让Java开发者能够用面向对象的方式优雅处理关联查询。本文将带你从零构建一个用户-部门联查系统,完整覆盖环境搭建、注解配置、CRUD操作等实战要点。

1. 环境准备与项目初始化

1.1 创建SpringBoot项目

使用Spring Initializr创建项目时,需勾选以下依赖:

  • Spring Web
  • Lombok
  • MySQL Driver

然后在pom.xml中追加MPJ核心依赖:

<dependency> <groupId>com.github.yulichang</groupId> <artifactId>mybatis-plus-join-boot-starter</artifactId> <version>1.4.11</version> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.5</version> </dependency>

1.2 数据库配置

application.yml中配置数据源和MP基本参数:

spring: datasource: url: jdbc:mysql://localhost:3306/mpj_demo?useSSL=false username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0

2. 实体与Mapper设计

2.1 实体类注解配置

用户实体(SysUser.java)示例:

@Data @TableName("sys_user") public class SysUser { @TableId(type = IdType.AUTO) private Long id; private Long deptId; private String userName; private String nickName; @TableField(exist = false) private String deptName; // 非数据库字段,用于接收联查结果 }

部门实体(SysDept.java)配置一对一关联:

@Data @TableName("sys_dept") public class SysDept { @TableId(type = IdType.AUTO) private Long id; private String deptName; }

2.2 Mapper接口继承链

用户Mapper需继承MPJBaseMapper:

public interface SysUserMapper extends MPJBaseMapper<SysUser> { }

Service层同样需要特殊继承:

public interface ISysUserService extends MPJBaseService<SysUser> { }

3. 联表查询实战技巧

3.1 基础左连接查询

实现用户列表带部门名称的查询:

public List<UserDTO> getUserListWithDept() { MPJLambdaWrapper<SysUser> wrapper = new MPJLambdaWrapper<>() .selectAll(SysUser.class) .select(SysDept::getDeptName) .leftJoin(SysDept.class, SysDept::getId, SysUser::getDeptId); return sysUserService.selectJoinList(UserDTO.class, wrapper); }

注意:UserDTO需要包含SysUser所有字段+deptName属性

3.2 复杂条件分页查询

带条件的分页联查示例:

public Page<UserDTO> searchUsers(SearchParam param) { MPJLambdaWrapper<SysUser> wrapper = new MPJLambdaWrapper<>() .selectAs(SysDept::getDeptName, UserDTO::getDeptName) .leftJoin(SysDept.class, SysDept::getId, SysUser::getDeptId) .like(param.getKeyword() != null, SysUser::getNickName, param.getKeyword()) .eq(param.getDeptId() != null, SysUser::getDeptId, param.getDeptId()); return sysUserService.selectJoinPage(new Page<>(param.getPage(), param.getSize()), UserDTO.class, wrapper); }

3.3 一对多关联查询

查询部门及其下属用户:

public List<DeptVO> getDeptWithUsers() { MPJLambdaWrapper<SysDept> wrapper = new MPJLambdaWrapper<>() .selectAll(SysDept.class) .selectCollection(SysUser.class, DeptVO::getUserList) .leftJoin(SysUser.class, SysUser::getDeptId, SysDept::getId); return sysDeptService.selectJoinList(DeptVO.class, wrapper); }

4. 联表增删改高级应用

4.1 关联更新操作

同时更新用户和部门信息:

public boolean updateUserAndDept(Long userId) { UpdateJoinWrapper<SysUser> wrapper = JoinWrappers.update(SysUser.class) .set(SysUser::getNickName, "新昵称") .set(SysDept::getDeptName, "新部门名") .leftJoin(SysDept.class, SysDept::getId, SysUser::getDeptId) .eq(SysUser::getId, userId); return sysUserService.updateJoin(null, wrapper); }

4.2 关联删除方案

根据条件删除主表数据:

public boolean deleteUserWithCondition(Long deptId) { DeleteJoinWrapper<SysUser> wrapper = JoinWrappers.delete(SysUser.class) .leftJoin(SysDept.class, SysDept::getId, SysUser::getDeptId) .eq(SysDept::getId, deptId); return sysUserService.deleteJoin(wrapper); }

级联删除关联表数据:

public boolean cascadeDelete(Long userId) { DeleteJoinWrapper<SysUser> wrapper = JoinWrappers.delete(SysUser.class) .deleteAll() .leftJoin(SysDept.class, SysDept::getId, SysUser::getDeptId) .eq(SysUser::getId, userId); return sysUserService.deleteJoin(wrapper); }

5. 性能优化与踩坑指南

5.1 N+1查询问题解决

MPJ默认使用LEFT JOIN一次性加载数据,但需要注意:

  • 避免selectAll()在多层关联时使用
  • 复杂关联建议拆分为多次查询
  • 大数据量时添加@Transactional注解

5.2 缓存配置建议

在MybatisConfig中增加二级缓存:

@Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); // 添加缓存插件 interceptor.addInnerInterceptor(new CachingInnerInterceptor()); return interceptor; }

5.3 常见问题排查

  1. 字段映射失败

    • 检查DTO属性与selectAs别名是否一致
    • 确认表字段是否有下划线转驼峰问题
  2. 分页总数不准

    • 复杂联查建议使用@SqlParser(filter=true)
    • 或者手动指定countSql
  3. 事务失效场景

    • 跨Service调用需要添加@Transactional
    • 批量操作建议使用TransactionTemplate

实际项目中,在处理用户-角色-权限三级关联时,MPJ的链式API比传统XML方式减少约60%的代码量。特别是在动态条件查询场景,Wrapper的条件构造方式让复杂查询的维护成本大幅降低。

http://www.jsqmd.com/news/520329/

相关文章:

  • 瑞萨RA系列MCU LED控制与FSP工程化实践
  • Steam Economy Enhancer:基于用户脚本的Steam市场自动化交易系统架构设计与实战
  • YOLOv11涨点改进| CVPR 2026 |独家创新首发、Conv卷积改进篇 | 引入ConvLoRA卷积模块,自动选择和优化关键层,保持高精度和高效推理速度,含多种二次创新改进点,高效发论文
  • Arduino轻量级Modbus RTU从站库ModbusSlave详解
  • 乙巳马年·皇城大门春联生成终端W赋能LaTeX文档:自动化生成学术论文致谢或节日贺词
  • 基于Java的企业级应用集成:万象熔炉·丹青幻境API开发实战
  • ENVI决策树分类保姆级教程:用DEM和Landsat数据手把手教你做地物分类(附完整规则表达式)
  • STM32F103ZET6串口调试翻车实录:换了串口助手才解决,德飞莱尼莫M3S开发板实测
  • SUPER COLORIZER自动化测试:编写Python脚本进行批量图像上色与效果评估
  • mbed平台轻量级OSC协议实现与嵌入式音频控制
  • 基于CanFestival的CANopen主节点PDO通信实战指南
  • 《Claude Code 从入门到精通》试读篇:你的第一次 Director Mode 体验(二)
  • StructBERT模型对中文近义词、反义词的区分能力深度测试
  • MCCI FRAM I2C驱动:工业级嵌入式非易失存储实现
  • 基于GLM-4-9B-Chat-1M的智能会议助手:纪要生成与行动项跟踪
  • Arduino嵌入式单元测试:零硬件依赖的C++模拟框架
  • 用Canvas和JavaScript手搓一个会呼吸的炸弹动画(附完整源码)
  • YOLOv8多语言文档本地化指南:手把手教你贡献中文文档
  • 保姆级教程:如何通过COM_RCL_EXCEPT参数解决PX4 offboard模式起飞问题
  • Qwen3-Embedding-4B一文详解:4B参数模型相比1B/8B的向量表征跃迁
  • HG-ha/MTools多平台对比:Windows/macOS/Linux三端AI功能完整性与GPU利用率报告
  • Qt高精度定时需求救星:手把手教你用QThread+msleep实现稳定毫秒级定时(附线程安全代码)
  • 探索桌面光标美学:打造个性化视觉交互体验
  • 告别混乱!用这3步搞定Pandas透视表的行列索引转换
  • Fish Speech-1.5镜像免配置部署指南:开箱即用的开源TTS方案
  • 告别枯燥数据!用Unity的Chart And Graph插件5分钟搞定游戏内动态排行榜(附完整配置流程)
  • Flask SSTI漏洞实战:从BUUCTF靶场到手工Payload构造全解析
  • 作品欣赏:梦幻动漫魔法工坊创作的梦幻风格二次元角色
  • 别再只会用rm了!Linux下彻底删除文件的正确姿势(附truncate使用指南)
  • ROS1项目实战:如何像官方工具一样,用Python模块化组织你的rospy代码