MyBatis-Plus 多表查询极简实践:宠物管理系统场景落地
MyBatis-Plus(简称 MP)的单表增删改查早已通过 BaseMapper 和 LambdaQueryChainWrapper 实现"零 SQL"极简开发,无需过多赘述。但实际业务中,"查询宠物同时显示主人信息""统计某主人的所有宠物"这类多表关联需求无处不在。本文将用费曼学习法的核心思路------把复杂问题变简单,用实际场景落地,分享 MP 多表查询的最简洁方案,全程基于宠物管理系统场景给出可直接运行的代码。
核心思路:拒绝复杂配置,优先"注解+Lambda"
MP 多表查询无需额外引入插件(如 PageHelper 仅用于分页,非多表核心),最简洁的方案是 "@TableName 关联表名 + @TableField 绑定关联字段 + Lambda 条件构造器",本质是通过 MP 封装的条件构造器拼接关联查询 SQL,避免手动写 XML 或注解 SQL 的冗余。
核心原则:能少写一行代码就少写,能复用单表逻辑就复用,只关注"关联哪些表、关联条件是什么、要查哪些字段"。
场景定义:宠物管理系统核心表
先明确两个核心表的结构(简化设计,只保留关键字段):
- 主人表(owner):存储主人基本信息
- id(主键)、name(主人姓名)、phone(联系方式)
- 宠物表(pet):存储宠物信息,通过 owner_id 关联主人表
- id(主键)、name(宠物姓名)、type(宠物类型,如猫/狗)、owner_id(外键,关联 owner.id)
第一步:定义实体类(关联关系映射)
实体类需通过注解绑定表名和关联字段,无需额外配置 XML 映射文件。
1. 主人实体(Owner)
java
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
@Data
@TableName("owner") // 绑定数据库表名
public class Owner {
@TableId(type = IdType.AUTO) // 自增主键
private Long id;
private String name; // 主人姓名
private String phone; // 联系方式
}
2. 宠物实体(Pet)
java
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
@Data
@TableName("pet") // 绑定数据库表名
public class Pet {
@TableId(type = IdType.AUTO)
private Long id;
private String name; // 宠物姓名
private String type; // 宠物类型
@TableField("owner_id") // 绑定数据库外键字段(若属性名与字段名一致可省略)
private Long ownerId; // 关联主人表的主键
// 非数据库字段:用于存储关联查询的主人信息(MP 会自动忽略非表字段)
@TableField(exist = false)
private Owner owner;
}
关键说明:
@TableName:指定实体类对应的数据库表名,解决"类名与表名不一致"问题;@TableField(exist = false):标记非数据库字段,避免 MP 解析时报错(用于存储关联查询的关联对象);- 实体类属性名遵循"驼峰命名",数据库字段遵循"下划线命名"(如 ownerId ↔ owner_id),MP 会自动映射,无需额外配置。
第二步:定义 Mapper 接口(继承 BaseMapper)
MP 的 Mapper 接口只需继承 BaseMapper,即可获得单表操作能力,多表查询通过"条件构造器 + 自定义 SQL 片段"实现(无需写完整 SQL)。
1. 主人 Mapper(OwnerMapper)
java
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.springframework.stereotype.Repository;
@Repository
public interface OwnerMapper extends BaseMapper<Owner> {
// 单表操作已通过 BaseMapper 实现,无需额外写方法
}
2. 宠物 Mapper(PetMapper)
java
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.springframework.stereotype.Repository;
@Repository
public interface PetMapper extends BaseMapper<Pet> {
// 后续多表查询的自定义方法将在这里扩展(极简方案无需额外方法)
}
第三步:多表查询核心实现(3 个高频场景)
基于 MP 的 LambdaQueryChainWrapper 和 QueryWrapper,结合"关联字段拼接",实现无 XML、少代码的多表查询。
场景 1:查询宠物列表,同时显示所属主人信息(一对一关联)
需求:查询所有"狗"类宠物,返回宠物的 id、姓名、类型,以及主人的姓名、联系方式。
实现思路:
- 用
QueryWrapper拼接LEFT JOIN关联 owner 表; - 通过
select指定需要查询的字段(宠物表字段 + 主人表字段); - 用
listMaps()获取结果集(Map 结构,key 为字段名),再手动映射到 Pet 实体(因 MP 不直接支持关联对象自动映射,极简方案用手动映射避免复杂配置)。
代码实现:
java
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Service
public class PetService {
@Resource
private PetMapper petMapper;
@Resource
private OwnerMapper ownerMapper;
// 查询所有狗类宠物及所属主人信息
public List<Pet> getDogWithOwner() {
// 1. 构建查询条件:LEFT JOIN owner 表,关联条件 pet.owner_id = owner.id,筛选宠物类型为狗
QueryWrapper<Pet> queryWrapper = new QueryWrapper<Pet>()
.leftJoin("owner", "pet.owner_id = owner.id") // 关联表名 + 关联条件
.eq("pet.type", "狗") // 宠物类型筛选
.select(
"pet.id", "pet.name", "pet.type", // 宠物表字段
"owner.name as owner_name", "owner.phone as owner_phone" // 主人表字段(别名避免冲突)
);
// 2. 执行查询,获取 Map 结构结果集
List<Map<String, Object>> resultMaps = petMapper.selectMaps(queryWrapper);
// 3. 映射为 Pet 实体(极简映射,无需工具类)
return resultMaps.stream().map(map -> {
Pet pet = new Pet();
// 宠物字段映射
pet.setId(Long.parseLong(map.get("id").toString()));
pet.setName(map.get("name").toString());
pet.setType(map.get("type").toString());
// 主人字段映射
Owner owner = new Owner();
owner.setName(map.get("owner_name").toString());
owner.setPhone(map.get("owner_phone").toString());
pet.setOwner(owner);
return pet;
}).collect(Collectors.toList());
}
}
场景 2:查询某主人的所有宠物(一对多关联)
需求:根据主人姓名查询该主人的所有宠物,返回主人信息 + 宠物列表。
实现思路:
- 先通过主人姓名查询主人实体(单表查询);
- 再通过主人 id 查询所有关联的宠物(单表查询 + 条件筛选);
- 组合结果(极简方案:先查主表,再查从表,避免复杂关联查询)。
代码实现:
java
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryChainWrapper;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;
@Service
public class OwnerService {
@Resource
private OwnerMapper ownerMapper;
@Resource
private PetMapper petMapper;
// 根据主人姓名查询其所有宠物
public Owner getOwnerWithPets(String ownerName) {
// 1. 单表查询:获取指定姓名的主人(假设姓名唯一)
Owner owner = new LambdaQueryChainWrapper<>(ownerMapper)
.eq(Owner::getName, ownerName)
.one();
if (owner == null) {
return null;
}
// 2. 单表查询:根据主人 id 查询所有宠物(一对多关联)
List<Pet> pets = new LambdaQueryChainWrapper<>(petMapper)
.eq(Pet::getOwnerId, owner.getId()) // 关联条件:pet.owner_id = owner.id
.list();
// 3. 组合结果
owner.setPets(pets); // 注意:需在 Owner 实体中添加 List<Pet> pets 字段(@TableField(exist = false))
return owner;
}
}
场景 3:分页查询宠物及主人信息(带分页的多表查询)
需求:分页查询宠物列表,每页 10 条,显示宠物和主人信息,支持按宠物类型筛选。
实现思路:
- 用 MP 的
Page对象封装分页参数(页码、每页条数); - 结合
QueryWrapper实现关联查询和条件筛选; - 用
page()方法执行分页查询,自动返回分页结果(总条数、当前页数据)。
代码实现:
java
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Service
public class PetService {
// 分页查询宠物及主人信息(按类型筛选)
public Page<Pet> getPetWithOwnerByPage(int pageNum, int pageSize, String petType) {
// 1. 构建分页对象(pageNum:页码,pageSize:每页条数)
Page<Map<String, Object>> page = new Page<>(pageNum, pageSize);
// 2. 构建关联查询条件
QueryWrapper<Pet> queryWrapper = new QueryWrapper<Pet>()
.leftJoin("owner", "pet.owner_id = owner.id")
.eq(petType != null, "pet.type", petType) // 条件筛选:宠物类型(非空才添加)
.select(
"pet.id", "pet.name", "pet.type",
"owner.name as owner_name", "owner.phone as owner_phone"
);
// 3. 执行分页查询(返回 Map 结构的分页结果)
Page<Map<String, Object>> resultPage = petMapper.selectMapsPage(page, queryWrapper);
// 4. 映射为 Pet 实体的分页结果
List<Pet> petList = resultPage.getRecords().stream().map(map -> {
Pet pet = new Pet();
pet.setId(Long.parseLong(map.get("id").toString()));
pet.setName(map.get("name").toString());
pet.setType(map.get("type").toString());
Owner owner = new Owner();
owner.setName(map.get("owner_name").toString());
owner.setPhone(map.get("owner_phone").toString());
pet.setOwner(owner);
return pet;
}).collect(Collectors.toList());
// 5. 封装分页结果(复用原分页对象的总条数、页码等信息)
Page<Pet> petPage = new Page<>();
petPage.setRecords(petList);
petPage.setTotal(resultPage.getTotal());
petPage.setSize(resultPage.getSize());
petPage.setCurrent(resultPage.getCurrent());
return petPage;
}
}
关键说明:为什么这是"极简方案"?
- 无 XML 配置:全程通过注解和 Java 代码实现,无需编写 Mapper.xml 文件;
- 少自定义 SQL:仅通过
leftJoin和select拼接关联逻辑,无需写完整的SELECT ... FROM ... JOIN ...语句; - 复用单表能力:Mapper 接口仅继承 BaseMapper,无需额外扩展复杂方法;
- 映射简单:手动映射关联字段,避免引入
ResultMap或第三方映射工具,降低学习成本。
注意事项
- 关联条件必须明确:
leftJoin的第二个参数需写完整关联条件(如pet.owner_id = owner.id),避免字段冲突; - 字段别名:当关联表有同名字段时(如 name),需用
as指定别名(如owner.name as owner_name); - 分页依赖:分页查询需在 Spring 容器中配置 MP 的分页插件(否则分页不生效),配置代码如下:
java
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();
// 添加分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return interceptor;
}
}
总结
MyBatis-Plus 多表查询的核心是"用 MP 封装的条件构造器简化关联逻辑",无需陷入复杂的配置和插件中。本文通过宠物管理系统的 3 个高频场景,展示了"注解映射 + 条件构造器 + 简单映射"的极简方案,既满足业务需求,又保持了代码的简洁性和可读性。
记住:多表查询无需追求"全自动映射",在中小项目中,"手动映射 + 单表查询组合"是性价比最高的选择,既降低了学习成本,又便于调试和维护。