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

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)

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 的 LambdaQueryChainWrapperQueryWrapper,结合"关联字段拼接",实现无 XML、少代码的多表查询。

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

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

实现思路:
  1. QueryWrapper 拼接 LEFT JOIN 关联 owner 表;
  2. 通过 select 指定需要查询的字段(宠物表字段 + 主人表字段);
  3. 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:查询某主人的所有宠物(一对多关联)

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

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

实现思路:
  1. 用 MP 的 Page 对象封装分页参数(页码、每页条数);
  2. 结合 QueryWrapper 实现关联查询和条件筛选;
  3. 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;
    }
}

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

  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 的分页插件(否则分页不生效),配置代码如下:
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 个高频场景,展示了"注解映射 + 条件构造器 + 简单映射"的极简方案,既满足业务需求,又保持了代码的简洁性和可读性。

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

相关推荐
小二·5 小时前
MyBatis基础入门《七》ResultMap 高级映射:一对一 & 一对多关联查询
mybatis
fanruitian5 小时前
springboot4 swagger3
java·springboot·swagger3·springboot4
⑩-5 小时前
JVM-内存模型
java·jvm
小年糕是糕手5 小时前
【C++】内存管理(上)
java·开发语言·jvm·c++·算法·spring·servlet
Qiuner6 小时前
Spring Boot 机制五: Bean 生命周期与后置处理器(BeanPostProcessor)源码深度剖析
java·spring boot·后端
路边草随风6 小时前
java实现发布flink yarn session模式作业
java·flink·yarn
qq_12498707536 小时前
基于Spring Boot的阳光餐盘点餐系统(源码+论文+部署+安装)
java·vue.js·spring boot·后端·毕业设计
程序员三明治6 小时前
【Java基础】序列化到底是什么?有什么用?实现原理?
java·开发语言·后端·java基础·序列化·反序列化
Full Stack Developme6 小时前
Java实现Word、Excel、PDF文件 在线预览
java·word·excel