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的条件构造方式让复杂查询的维护成本大幅降低。