【MybatisPlus】join关联查询MPJLambdaWrapper
【MybatisPlus】join关联查询MPJLambdaWrapper
- 【一】前期准备
- 【1】引入依赖
- 【2】核心组件说明
- 【二】基础案例:一对一关联查询
- 【1】场景
- 【2】定义实体类
- 【3】定义 DTO(接收关联结果)
- 【4】自定义 Mapper 接口
- 【5】关联查询实现
- 【6】执行结果说明
- 【三】进阶案例:一对多关联查询
- 【1】场景
- 【2】定义订单实体和 DTO
- 【3】一对多关联查询实现
- 【四】分页关联查询
- 【五】关键使用细节
- 【1】关联条件语法
- 【2】字段别名与 DTO 映射
- 【3】条件查询
- 【4】只查询部分字段
- 【5】一对多结果封装
MyBatis-Plus 本身并未直接提供关联查询(Join)的内置 API,但其生态中有一个常用的扩展库 mybatis-plus-join(官方维护的扩展),可以通过 MPJLambdaWrapper 实现类似 MyBatis 的关联查询功能,且语法更简洁。以下是其使用细节及详细案例。
【一】前期准备
【1】引入依赖
在 pom.xml 中添加 mybatis-plus-join 依赖(需与 MyBatis-Plus 版本兼容,推荐 1.4.4+):
<dependency><groupId>com.github.yulichang</groupId><artifactId>mybatis-plus-join-boot-starter</artifactId><version>1.4.4</version>
</dependency>
【2】核心组件说明
(1)MPJLambdaWrapper:关联查询的核心条件构造器,支持 leftJoin、rightJoin、innerJoin 等关联方式。
(2)MPJBaseMapper:需自定义 Mapper 继承该接口,以获得关联查询的方法(如 selectJoinList、selectJoinPage)。
(3)DTO 类:用于接收关联查询的结果(包含多表字段),需与查询的字段映射。
【二】基础案例:一对一关联查询
【1】场景
主表:user(用户表),字段:id、name、age、address_id
关联表:address(地址表),字段:id、province、city、detail
需求:查询用户信息及关联的地址信息(user.address_id = address.id)
【2】定义实体类
// 用户表实体
@Data
@TableName("user")
public class User {private Long id;private String name;private Integer age;private Long addressId; // 关联地址表的id
}// 地址表实体
@Data
@TableName("address")
public class Address {private Long id;private String province;private String city;private String detail;
}
【3】定义 DTO(接收关联结果)
// 包含用户和地址的关联结果
@Data
public class UserAddressDTO {// 用户表字段private Long userId; // 对应user.id(注意别名,避免与address.id冲突)private String userName; // 对应user.nameprivate Integer userAge; // 对应user.age// 地址表字段private Long addressId; // 对应address.idprivate String province;private String city;private String detail;
}
【4】自定义 Mapper 接口
// 用户Mapper,继承MPJBaseMapper以支持关联查询
public interface UserMapper extends MPJBaseMapper<User> {
}
【5】关联查询实现
@Service
public class UserService {@Autowiredprivate UserMapper userMapper;/*** 查询用户及关联的地址信息*/public List<UserAddressDTO> getUserWithAddress() {// 构建关联查询条件MPJLambdaWrapper<User> wrapper = new MPJLambdaWrapper<User>()// 主表:user,查询字段(别名映射到DTO).select(User::getId, User::getName, User::getAge).selectAs(User::getId, UserAddressDTO::getUserId) // user.id -> userId.selectAs(User::getName, UserAddressDTO::getUserName) // user.name -> userName.selectAs(User::getAge, UserAddressDTO::getUserAge) // user.age -> userAge// 左关联地址表(address),别名a.leftJoin(Address.class, Address::getId, User::getAddressId, "a")// 关联表字段(别名a的字段映射到DTO).select("a.id AS addressId") // address.id -> addressId.select("a.province").select("a.city").select("a.detail")// 条件:查询年龄>18的用户.gt(User::getAge, 18);// 执行关联查询,返回DTO列表return userMapper.selectJoinList(UserAddressDTO.class, wrapper);}
}
【6】执行结果说明
生成的 SQL 类似:
SELECT t.id AS userId, t.name AS userName, t.age AS userAge, a.id AS addressId, a.province, a.city, a.detail
FROM user t
LEFT JOIN address a ON a.id = t.address_id
WHERE t.age > 18
【三】进阶案例:一对多关联查询
【1】场景
主表:user(用户表)
关联表:order(订单表),字段:id、user_id、amount、create_time
需求:查询用户信息及关联的所有订单(order.user_id = user.id)
【2】定义订单实体和 DTO
// 订单表实体
@Data
@TableName("order")
public class Order {private Long id;private Long userId; // 关联用户表的idprivate BigDecimal amount;private LocalDateTime createTime;
}// 包含用户和订单列表的DTO
@Data
public class UserOrderDTO {private Long userId;private String userName;private List<Order> orders; // 一对多:用户的多个订单
}
【3】一对多关联查询实现
public List<UserOrderDTO> getUserWithOrders() {MPJLambdaWrapper<User> wrapper = new MPJLambdaWrapper<User>()// 主表字段.selectAs(User::getId, UserOrderDTO::getUserId).selectAs(User::getName, UserOrderDTO::getUserName)// 左关联订单表(order),别名o.leftJoin(Order.class, Order::getUserId, User::getId, "o")// 关联表字段(全部订单字段).select("o.id", "o.user_id", "o.amount", "o.create_time")// 条件:订单金额>100.gt(Order::getAmount, new BigDecimal("100"));// 执行查询,MyBatis-Plus会自动将同一用户的订单封装到List<Order>中return userMapper.selectJoinList(UserOrderDTO.class, wrapper);
}
【四】分页关联查询
在一对一查询基础上,实现分页查询(每页 10 条)。
public IPage<UserAddressDTO> getUserWithAddressPage(int pageNum, int pageSize) {// 构建分页对象Page<User> page = new Page<>(pageNum, pageSize);// 关联查询条件(同基础案例)MPJLambdaWrapper<User> wrapper = new MPJLambdaWrapper<User>().selectAs(User::getId, UserAddressDTO::getUserId).selectAs(User::getName, UserAddressDTO::getUserName).leftJoin(Address.class, Address::getId, User::getAddressId, "a").select("a.province", "a.city").eq(User::getName, "张三"); // 按用户名筛选// 执行分页关联查询return userMapper.selectJoinPage(page, UserAddressDTO.class, wrapper);
}
【五】关键使用细节
【1】关联条件语法
leftJoin(关联表实体类, 关联表关联字段, 主表关联字段, 关联表别名)
例如:leftJoin(Address.class, Address::getId, User::getAddressId, “a”) 等价于 LEFT JOIN address a ON a.id = t.address_id(t 是主表默认别名)。
【2】字段别名与 DTO 映射
当多表字段名冲突(如 id)时,必须用 selectAs 或 select(“别名.字段 AS DTO属性”) 指定别名,否则会导致字段覆盖。
DTO 中的属性名需与查询的别名一致(支持驼峰映射,如 user_id 可映射到 userId)。
【3】条件查询
可通过 eq、gt、like 等方法添加主表或关联表的条件,关联表条件需用别名或 Lambda 指定,例如:
.eq(“a.province”, “广东省”) 或 .eq(Address::getProvince, “广东省”)(需确保关联表已添加)。
【4】只查询部分字段
尽量避免使用 selectAll(实体类)(查询所有字段),而是通过 select 精确指定所需字段,减少数据传输。
【5】一对多结果封装
当关联表为 “多” 的一方时(如一个用户多个订单),DTO 中需定义 List<关联实体> 类型的属性,MyBatis-Plus 会自动按主表 ID 分组封装。