MyBatis-Plus 多表查询极简实践:宠物管理系统场景落地

MyBatis-Plus(简称 MP)的单表增删改查早已通过 BaseMapper 和 LambdaQueryChainWrapper 实现“零 SQL”极简开发,无需过多赘述。但实际业务中,“查询宠物同时显示主人信息”“统计某主人的所有宠物”这类多表关联需求无处不在。本文将用费曼学习法的核心思路——把复杂问题变简单,用实际场景落地,分享 MP 多表查询的最简洁方案,全程基于宠物管理系统场景给出可直接运行的代码。

核心思路:拒绝复杂配置,优先“注解+Lambda”

MP 多表查询无需额外引入插件(如 PageHelper 仅用于分页,非多表核心),最简洁的方案是 “@TableName 关联表名 + @TableField 绑定关联字段 + Lambda 条件构造器”,本质是通过 MP 封装的条件构造器拼接关联查询 SQL,避免手动写 XML 或注解 SQL 的冗余。

核心原则:能少写一行代码就少写,能复用单表逻辑就复用,只关注“关联哪些表、关联条件是什么、要查哪些字段”。

场景定义:宠物管理系统核心表

先明确两个核心表的结构(简化设计,只保留关键字段):

  1. 主人表(owner):存储主人基本信息
    • id(主键)、name(主人姓名)、phone(联系方式)
  2. 宠物表(pet):存储宠物信息,通过 owner_id 关联主人表
    • id(主键)、name(宠物姓名)、type(宠物类型,如猫/狗)、owner_id(外键,关联 owner.id)

第一步:定义实体类(关联关系映射)

实体类需通过注解绑定表名和关联字段,无需额外配置 XML 映射文件。

1. 主人实体(Owner)

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)

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)

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.springframework.stereotype.Repository;
@Repository
public interface OwnerMapper extends BaseMapper<Owner> {
  // 单表操作已通过 BaseMapper 实现,无需额外写方法
  }

2. 宠物 Mapper(PetMapper)

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.springframework.stereotype.Repository;
@Repository
public interface PetMapper extends BaseMapper<Pet> {
  // 后续多表查询的自定义方法将在这里扩展(极简方案无需额外方法)
  }

第三步:多表查询核心实现(3 个高频场景)

基于 MP 的 LambdaQueryChainWrapperQueryWrapper,结合“关联字段拼接”,实现无 XML、少代码的多表查询。

场景 1:查询宠物列表,同时显示所属主人信息(一对一关联)

需求:查询所有“狗”类宠物,返回宠物的 id、姓名、类型,以及主人的姓名、联系方式。

实现思路:
  1. QueryWrapper 拼接 LEFT JOIN 关联 owner 表;
  2. 通过 select 指定需要查询的字段(宠物表字段 + 主人表字段);
  3. listMaps() 获取结果集(Map 结构,key 为字段名),再手动映射到 Pet 实体(因 MP 不直接支持关联对象自动映射,极简方案用手动映射避免复杂配置)。
代码实现:
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:查询某主人的所有宠物(一对多关联)

需求:根据主人姓名查询该主人的所有宠物,返回主人信息 + 宠物列表。

实现思路:
  1. 先通过主人姓名查询主人实体(单表查询);
  2. 再通过主人 id 查询所有关联的宠物(单表查询 + 条件筛选);
  3. 组合结果(极简方案:先查主表,再查从表,避免复杂关联查询)。
代码实现:
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 条,显示宠物和主人信息,支持按宠物类型筛选。

实现思路:
  1. 用 MP 的 Page 对象封装分页参数(页码、每页条数);
  2. 结合 QueryWrapper 实现关联查询和条件筛选;
  3. page() 方法执行分页查询,自动返回分页结果(总条数、当前页数据)。
代码实现:
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;
            }
            }

关键说明:为什么这是“极简方案”?

  1. 无 XML 配置:全程通过注解和 Java 代码实现,无需编写 Mapper.xml 文件;
  2. 少自定义 SQL:仅通过 leftJoinselect 拼接关联逻辑,无需写完整的 SELECT ... FROM ... JOIN ... 语句;
  3. 复用单表能力:Mapper 接口仅继承 BaseMapper,无需额外扩展复杂方法;
  4. 映射简单:手动映射关联字段,避免引入 ResultMap 或第三方映射工具,降低学习成本。

注意事项

  1. 关联条件必须明确:leftJoin 的第二个参数需写完整关联条件(如 pet.owner_id = owner.id),避免字段冲突;
  2. 字段别名:当关联表有同名字段时(如 name),需用 as 指定别名(如 owner.name as owner_name);
  3. 分页依赖:分页查询需在 Spring 容器中配置 MP 的分页插件(否则分页不生效),配置代码如下:
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 个高频场景,展示了“注解映射 + 条件构造器 + 简单映射”的极简方案,既满足业务需求,又保持了代码的简洁性和可读性。

记住:多表查询无需追求“全自动映射”,在中小项目中,“手动映射 + 单表查询组合”是性价比最高的选择,既降低了学习成本,又便于调试和维护。