MyBatis-Plus 实战:MPJLambdaWrapper 多表联查用法全解析

在 MyBatis-Plus(简称 MP)的日常使用中,单表查询通过 LambdaWrapper 就能轻松实现,但面对多表联查场景,很多开发者会陷入 "是否要退回原生 SQL" 的困惑。其实 MP 提供的MPJLambdaWrapper(多表联查 Lambda 封装)能完美解决这一问题,它既保留了 Lambda 表达式的类型安全优势,又支持灵活的多表关联逻辑。本文将从基础到进阶,带大家掌握 MPJLambdaWrapper 多表联查的核心用法。

一、MPJLambdaWrapper 是什么?为什么需要它?

在了解具体用法前,我们先明确MPJLambdaWrapper的定位:它是 MP 扩展的多表查询封装类,基于LambdaWrapper增强而来,专门用于处理多表关联查询场景。

1. 解决传统多表查询的痛点

  • 原生 SQL 联查:需手动拼接表名、关联条件,容易出现字段名拼写错误,且无法享受 MP 的字段自动映射优势;
  • 普通 LambdaWrapper:仅支持单表操作,无法直接添加表关联逻辑;
  • MPJLambdaWrapper:支持通过 Lambda 表达式指定关联表、关联条件、字段映射,兼顾 "类型安全" 与 "开发效率"。

2. 核心优势

  • 类型安全:通过实体类 Lambda 表达式指定字段,避免硬编码字段名和表名,减少运行时错误;
  • 链式调用:支持与 LambdaWrapper 类似的链式语法,代码可读性更高;
  • 灵活映射:可自定义多表查询结果的返回结构,无需依赖固定实体类;
  • 兼容 MP 特性:支持分页、条件判断、排序等 MP 原生功能,无缝衔接现有代码。

二、前置准备:环境配置与依赖

使用 MPJLambdaWrapper 前,需确保项目已正确配置 MyBatis-Plus 环境,且引入相关依赖(以 Spring Boot 项目为例)。

1. 核心依赖

在pom.xml中添加 MP 核心依赖(建议使用 3.5.0 + 版本,确保多表联查功能稳定):

XML 复制代码
<!-- MyBatis-Plus Spring Boot Starter -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
    <version>3.5.5</version>
</dependency>

<!-- MP多表联查扩展(部分版本需单独引入) -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-extension</artifactId>
    <version>3.5.5</version>
</dependency>

2. 实体类与表结构准备

为方便后续案例演示,我们定义两个关联表:

  • 用户表(user):id(主键)、username(用户名)、dept_id(部门 ID,外键);
  • 部门表(dept):id(主键)、dept_name(部门名称)、status(状态:0 - 禁用,1 - 启用)。

对应的实体类如下:

java 复制代码
// User实体类

@Data
@TableName("user")
public class User {

    @TableId(type = IdType.AUTO)
    private Long id;

    private String username;

    private Long deptId; // 关联dept表的id

}


// Dept实体类

@Data
@TableName("dept")
private class Dept {

    @TableId(type = IdType.AUTO)
    private Long id;

    private String deptName;

    private Integer status;

}

三、MPJLambdaWrapper 多表联查实战

MPJLambdaWrapper 的核心是通过join方法指定关联表,结合select方法自定义返回字段,最终通过list或page方法执行查询。下面分场景讲解具体用法。

1. 基础场景:两表内连接(INNER JOIN)

需求:查询 "启用状态的部门下的所有用户",返回 "用户名、部门名称"。

实现步骤:
  1. 创建MPJLambdaWrapper对象,指定主表(User);
  1. 通过join方法添加关联表(Dept),并设置关联条件(user.dept_id = dept.id);
  1. 通过eq方法添加过滤条件(dept.status = 1);
  1. 通过select方法指定返回字段;
  1. 调用 Mapper 的selectList方法执行查询。
代码示例:
java 复制代码
@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;

    public List<Map<String, Object>> getUserWithDept() {

    // 1. 创建MPJLambdaWrapper,主表为User
    MPJLambdaWrapper<User> wrapper = new MPJLambdaWrapper<>(User.class);

    // 2. 内连接Dept表,关联条件:user.deptId = dept.id
    wrapper.join(Dept.class, Dept::getId, User::getDeptId)
    // 3. 过滤条件:部门状态为启用(1)
    .eq(Dept::getStatus, 1)
    // 4. 指定返回字段:用户名(user.username)、部门名称(dept.deptName)
    .select(User::getUsername, Dept::getDeptName);
    // 5. 执行查询,返回Map结构(key为字段名,value为字段值)
    return userMapper.selectList(wrapper);

    }

}
生成的 SQL 逻辑:
sql 复制代码
SELECT
    user.username,
    dept.dept_name
FROM
    user
INNER JOIN
    dept ON user.dept_id = dept.id
WHERE
    dept.status = 1

2. 进阶场景 1:左连接(LEFT JOIN)与分页查询

需求:查询 "所有用户及其所属部门信息(无部门的用户也需显示)",支持分页,返回 "用户 ID、用户名、部门名称"。

关键差异:
  • 左连接需通过joinLeft方法指定,而非默认的内连接;
  • 分页查询需结合 MP 的IPage对象,通过selectPage方法执行。
代码示例:
java 复制代码
public IPage<Map<String, Object>> getUserWithDeptPage(Integer pageNum, Integer pageSize) {

    // 1. 构建分页对象(pageNum:当前页,pageSize:每页条数)
    IPage<User> page = new Page<>(pageNum, pageSize);

    // 2. 创建MPJLambdaWrapper,主表为User
    MPJLambdaWrapper<User> wrapper = new MPJLambdaWrapper<>(User.class);

    // 3. 左连接Dept表,关联条件:user.deptId = dept.id
    wrapper.joinLeft(Dept.class, Dept::getId, User::getDeptId)
    // 指定返回字段:用户ID、用户名、部门名称
    .select(User::getId, User::getUsername, Dept::getDeptName);

    // 4. 执行分页查询
    return userMapper.selectPage(page, wrapper);

}
生成的 SQL 逻辑:
sql 复制代码
SELECT
    user.id,
    user.username,
    dept.dept_name
FROM
    user
LEFT JOIN
    dept ON user.dept_id = dept.id
LIMIT
    #{pageNum-1}*#{pageSize}, #{pageSize}

3. 进阶场景 2:多表联查(三表及以上)

需求:新增 "角色表(role)",查询 "用户 - 部门 - 角色关联信息",返回 "用户名、部门名称、角色名称"。

新增角色表与关联表:
  • 角色表(role):id、role_name(角色名称);
  • 用户角色关联表(user_role):user_id(关联 user.id)、role_id(关联 role.id)。
代码示例:
java 复制代码
public List<Map<String, Object>> getUserDeptRole() {

    MPJLambdaWrapper<User> wrapper = new MPJLambdaWrapper<>(User.class);

    // 1. 左连接部门表
    wrapper.joinLeft(Dept.class, Dept::getId, User::getDeptId)
    // 2. 左连接用户角色关联表(user_role),关联条件:user.id = user_role.user_id
    .joinLeft(UserRole.class, UserRole::getUserId, User::getId)
    // 3. 左连接角色表(role),关联条件:user_role.role_id = role.id
    .joinLeft(Role.class, Role::getId, UserRole::getRoleId)
    // 4. 指定返回字段
    .select(User::getUsername, Dept::getDeptName, Role::getRoleName);

    return userMapper.selectList(wrapper);

}

4. 高级场景:自定义返回结果(非 Map 结构)

实际开发中,我们常需要将多表查询结果映射到自定义 DTO(数据传输对象),而非通用的 Map。此时可通过select方法的 Lambda 表达式指定 DTO 字段映射。

步骤:
  1. 定义自定义 DTO(如UserDeptDTO);
  1. 在select方法中通过DTO::字段指定映射关系。
代码示例:
java 复制代码
// 1. 定义自定义DTO

@Data
public class UserDeptDTO {

    private String username; // 用户名

    private String deptName; // 部门名称

}


// 2. 多表查询映射到DTO

public List<UserDeptDTO> getUserDeptDTO() {

    MPJLambdaWrapper<User> wrapper = new MPJLambdaWrapper<>(User.class);

    wrapper.join(Dept.class, Dept::getId, User::getDeptId)
    .eq(Dept::getStatus, 1)
    // 映射DTO字段:User::getUsername → UserDeptDTO::getUsername
    // Dept::getDeptName → UserDeptDTO::getDeptName
    .select(
        UserDeptDTO::getUsername, User::getUsername,
        UserDeptDTO::getDeptName, Dept::getDeptName

    );

    // 注意:需使用MP提供的selectList方法,指定返回DTO类型
    return userMapper.selectList(wrapper, UserDeptDTO.class);

}

四、常见问题与注意事项

1. 关联表字段冲突怎么办?

若多表存在同名字段(如create_time),需通过as方法给字段起别名,避免查询结果覆盖。

示例:

java 复制代码
wrapper.join(Dept.class, Dept::getId, User::getDeptId)
// 给user的create_time起别名user_create_time
.select(User::getCreateTime, "user_create_time")
// 给dept的create_time起别名dept_create_time
.select(Dept::getCreateTime, "dept_create_time");

2. 如何支持复杂关联条件(如多字段关联)?

若关联条件需多个字段匹配(如user.dept_id = dept.id AND user.tenant_id = dept.tenant_id),可通过join方法的重载版本实现:

java 复制代码
wrapper.join(Dept.class, deptWrapper -> {

    // 多字段关联条件
    deptWrapper.eq(Dept::getId, User::getDeptId)
    .eq(Dept::getTenantId, User::getTenantId);

});

3. 为什么查询结果为 null?

常见原因:

  • 关联条件错误(如外键字段匹配错误,User::getDeptId误写为User::getId);
  • 表名与实体类@TableName注解不一致;
  • 字段名与实体类@TableField注解不一致(若数据库字段为下划线命名,实体类需为驼峰命名,或通过@TableField指定)。

4. 是否支持子查询?

MPJLambdaWrapper 支持在where条件中嵌入子查询,通过inSql方法实现。例如:查询 "属于'技术部'的用户":

java 复制代码
wrapper.join(Dept.class, Dept::getId, User::getDeptId)
.inSql(Dept::getId, "SELECT id FROM dept WHERE dept_name = '技术部'");

五、总结

MPJLambdaWrapper作为 MyBatis-Plus 的多表联查利器,完美解决了 "类型安全" 与 "灵活联查" 的平衡问题。核心要点可总结为:

  1. 用join/joinLeft指定关联表与关联条件;
  1. 用select指定返回字段(支持 Map 或自定义 DTO);
  1. 兼容 MP 原生的分页、排序、条件过滤等功能;
  1. 避免字段冲突时需用as起别名,关联条件错误需检查实体类注解。

掌握它后,开发者无需再手写复杂的多表联查 SQL,既能提升开发效率,又能减少硬编码带来的 bug。建议在实际项目中结合具体场景灵活使用,进一步挖掘 MP 的高效开发能力。

相关推荐
ᐇ9591 小时前
Java LinkedList集合全面解析:双向链表的艺术与实战
java·开发语言·链表
luyun0202022 小时前
Windows 11操作更丝滑,绝了
java·运维·figma
码银2 小时前
【数据结构】顺序表
java·开发语言·数据结构
Boop_wu2 小时前
[Java EE] 计算机基础
java·服务器·前端
一个儒雅随和的男子2 小时前
多级缓存解决方案
spring boot·缓存
橘子海全栈攻城狮2 小时前
【源码+文档+调试讲解】基于Spring Boot的考务管理系统设计与实现 085
java·spring boot·后端·spring
神仙别闹2 小时前
基于QT(C++) 实现哈夫曼压缩(多线程)
java·c++·qt
百锦再2 小时前
第12章 测试编写
android·java·开发语言·python·rust·go·erlang