首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >MybatisPlus Pro 来了,CURD开发效率直接拉满!

MybatisPlus Pro 来了,CURD开发效率直接拉满!

作者头像
苏三说技术
发布2026-06-02 13:21:39
发布2026-06-02 13:21:39
620
举报
文章被收录于专栏:苏三说技术苏三说技术

大家好,我是苏三,又跟大家见面了。

前言

说到MyBatis,很多小伙伴都会用,但未必用得“惊艳”。

这个轻量级的持久层框架虽然灵活,但在日常的单表CRUD操作上,不得不面对重复代码多、开发效率低的现实问题。

我们来看一个典型的场景:

代码语言:javascript
复制
// 原生MyBatis:每个实体都要写XML,每个方法都要配SQL
<mapper namespace="com.example.UserMapper">
    <select id="selectById" resultType="User">
        SELECT * FROM user WHERE id = #{id}
    </select>
    <insert id="insert" useGeneratedKeys="true" keyProperty="id">
        INSERT INTO user(name, age) VALUES(#{name}, #{age})
    </insert>
    <update id="updateById">
        UPDATE user SET name = #{name}, age = #{age} WHERE id = #{id}
    </update>
    <delete id="deleteById">
        DELETE FROM user WHERE id = #{id}
    </delete>
    <!-- 还有分页、列表、批量... 无穷无尽 -->
</mapper>

最近有位小伙伴反馈说:“苏三哥,为什么一个简单的用户表,我要写一百多行的XML?单表操作不就是增删改查和分页吗,能不能一行代码搞定?”

说实话,听到这话我一点都不意外。

当年我们使用传统MyBatis时,第一反应也是一样的。

但用了MybatisPlus一段时间,发现它把我们从XML地狱里解救出来,确实爽。

不过日子久了,又开始觉得不够用了,每个接口还得写个Controller、Service、ServiceImpl……虽然比MyBatis强了不少,但还是有不少重复劳动。

直到最近,我深度体验了基于MybatisPlus抽取的 MybatisPlus Pro——只需继承一个BaseController,增删改查、分页、列表、排序、条件查询,全部开箱即用。

今天这篇文章就专门跟大家一起聊聊MybatisPlus Pro,希望对你会有所帮助。

一、为什么我们需要MybatisPlus Pro?

在深入讲解之前,我们先来盘点一下日常开发中那些“看似能忍、实则很烦”的场景。

痛点1:MybatisPlus的BaseMapper已经很方便了,但Service层还得重复写。

每个Service接口基本都是在定义findById、findAll、insert、update、delete等通用方法。

虽然MP提供了IService接口,但依然需要在每个ServiceImpl里调用BaseMapper的方法,没有从根本上解决重复劳动的底层逻辑

痛点2:Controller层还是得写。

很多小伙伴在工作中可能有这样的经历:需求方说要加一个新模块,需要做一套完整的增删改查接口。

咱们开始复制粘贴,改类名、改路径、改参数……搞完一两个模块还没啥,等需求方一次性提了七八个模块的需求时,光写这些“复制-粘贴-微调”的代码就要一两个小时,而且极其容易出错。

痛点3:代码质量参差不齐。

同样的增删改查,张三这样写,李四那样写。

有的参数校验做得严丝合缝,有的漏了异常处理,线上直接报错。

痛点4:条件查询代码重复率高。

每个Controller里都要对QueryWrapper进行各种eq、like、orderBy的配置,逻辑极其相似,写多了就会看到大量的重复代码。

有一句话说得好:最好的代码是没有代码。

如果能把这些几乎一模一样的代码全部自动化生成,我们就能把90%的精力释放出来,投入到真正的业务逻辑里去。

二、MybatisPlus Pro是什么?

MybatisPlus Pro是在MybatisPlus的基础上进行增强的工具,它的核心理念是“站在巨人的肩膀上”,只做增强不做改变。

从项目结构上看,MybatisPlus Pro就是在MybatisPlus的基础上增加更多的模板功能,利用Spring Boot进行开发,将通用功能抽取出来,形成一套开箱即用的基类体系。

说白了,MybatisPlus Pro的核心思路就是:在MybatisPlus已经“消灭”了Mapper层代码的基础上,继续“消灭”Service层和Controller层的重复代码,让开发者只需要关注核心的业务逻辑。

这里需要说明一下,MybatisPlus Pro目前有两种理解:一种是社区基于MP二次封装的项目实现方案(就像富贵同学那个版本),另一种是MybatisPlus官方在不断迭代中新增的Pro级特性。本文重点围绕前者的设计思路和实战价值展开。

下面,我从架构师视角给大家画一张架构图,方便理解MyBatis、MybatisPlus和MybatisPlus Pro这三者之间的关系:

三、快速上手

说了这么多理论,咱们直接上代码,感受一下Pro版的威力。

第一步:引入依赖

MybatisPlus Pro建立在MybatisPlus基础上,所以首先要引入MybatisPlus的starter依赖:

代码语言:javascript
复制
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.15</version>
</dependency>

官方在2025年11月30日发布了v3.5.15版本,支持SpringBoot 4.0.0和Jackson 3.0,推荐使用最新的稳定版本。

第二步:编写工具类

工具类是Pro版的核心基础设施,它提供了三个关键功能:

  1. 驼峰与下划线的互相转换——解决Java实体类字段名和数据库列名的自动映射问题
  2. 通过反射获取实体字段值——动态提取实体中各字段的值,为构建QueryWrapper做准备
  3. 基于实体类自动生成QueryWrapper——零代码构建查询条件,大大简化条件查询的编写

这里的关键逻辑需要详细讲一下:当一个实体对象(比如User对象)传入getQueryWrapper方法时,这个方法会通过Java反射获取这个类的所有声明的字段(用entity.getClass().getDeclaredFields()获取)。

然后遍历每一个字段,跳过那些被final修饰的字段(因为这些字段不能动态修改),再通过field.setAccessible(true)突破Java的访问权限限制,获取被private修饰的字段值。

如果字段值不为null,就把Java的驼峰字段名(如userName)转换成数据库的下划线列名(user_name),然后向QueryWrapper添加一个eq(等值匹配)条件。

这样一来,我们传入的实体里有哪些非空字段,QueryWrapper就自动用这些字段作为查询条件,完全不需要手动编写。

代码语言:javascript
复制
public class ApprenticeUtil {
    privatestatic Pattern humpPattern = Pattern.compile("[A-Z]");
    privatestatic Pattern linePattern = Pattern.compile("_(\\w)");
    
    // 驼峰转下划线(userName -> user_name)
    public static String humpToLine(String str) {
        Matcher matcher = humpPattern.matcher(str);
        StringBuffer sb = new StringBuffer();
        while (matcher.find()) {
            matcher.appendReplacement(sb, "_" + matcher.group(0).toLowerCase());
        }
        matcher.appendTail(sb);
        return sb.toString();
    }
    
    // 下划线转驼峰(user_name -> userName)
    public static String lineToHump(String str) {
        str = str.toLowerCase();
        Matcher matcher = linePattern.matcher(str);
        StringBuffer sb = new StringBuffer();
        while (matcher.find()) {
            matcher.appendReplacement(sb, matcher.group(1).toUpperCase());
        }
        matcher.appendTail(sb);
        return sb.toString();
    }
    
    // 根据非空字段生成QueryWrapper——这里是核心
    publicstatic <E> QueryWrapper<E> getQueryWrapper(E entity) {
        Field[] fields = entity.getClass().getDeclaredFields();
        QueryWrapper<E> eQueryWrapper = new QueryWrapper<>();
        for (Field field : fields) {
            // 忽略final字段
            if (Modifier.isFinal(field.getModifiers())) {
                continue;
            }
            field.setAccessible(true);
            try {
                Object obj = field.get(entity);
                if (obj != null) {
                    String name = humpToLine(field.getName());
                    eQueryWrapper.eq(name, obj);
                }
            } catch (IllegalAccessException e) {
                returnnull;
            }
        }
        return eQueryWrapper;
    }
}

第三步:编写通用BaseController

BaseController是整个Pro版的“发动机”,它集成了最核心的CRUD操作逻辑:

代码语言:javascript
复制
public class BaseController<S extends IService<T>, T> {
    @Autowired
    protected S baseService;
    
    @PostMapping("add")
    public Result add(@RequestBody T entity) {
        return Result.success(baseService.save(entity));
    }
    
    @PostMapping("update")
    public Result update(@RequestBody T entity) {
        return Result.success(baseService.updateById(entity));
    }
    
    @GetMapping("delete")
    public Result delete(String id) {
        return Result.success(baseService.removeById(id));
    }
    
    @GetMapping("detail")
    public Result detail(String id) {
        return Result.success(baseService.getById(id));
    }
    
    @PostMapping("list")
    public Result list(@RequestBody T entity) {
        // 利用工具类自动构建查询条件
        QueryWrapper<T> wrapper = ApprenticeUtil.getQueryWrapper(entity);
        if (wrapper == null) {
            wrapper = new QueryWrapper<>();
        }
        return Result.success(baseService.list(wrapper));
    }
    
    @PostMapping("page")
    public Result page(@RequestBody PageDto<T> pageDto) {
        T entity = pageDto.getEntity();
        QueryWrapper<T> wrapper = ApprenticeUtil.getQueryWrapper(entity);
        if (wrapper == null) {
            wrapper = new QueryWrapper<>();
        }
        IPage<T> page = new Page<>(pageDto.getPageNo(), pageDto.getPageSize());
        return Result.success(baseService.page(page, wrapper));
    }
}

这段代码的底层执行逻辑值得详细剖析。

当客户端通过/add接口发送POST请求时,Spring框架自动将JSON格式的请求体解析为T类型的实体对象。

然后baseService.save(entity)被调用,baseService实际上是MybatisPlus的IService接口的实现类,它内部持有一个BaseMapper。

save方法会自动判断:如果主键为null,执行insert插入操作;如果主键不为null,执行updateById更新操作。

MybatisPlus在应用启动时就会自动向Mapper中注入这些基本的CRUD操作,所以不需要我们写一行SQL。

这里特别说一下list方法:参数是T类型实体,方法内部直接用ApprenticeUtil.getQueryWrapper(entity)自动构建QueryWrapper。

比如前端传入一个只有name为“张三”的User对象(其他字段为null),那么框架会自动生成WHERE name = '张三'的查询条件。

所有非空字段都会被纳入查询条件,自动取交集。

这就实现了一个强大的能力——给什么字段查什么字段,完全零代码

第四步:实际使用

搞定了BaseController,我们再来创建一个ProductController,看看代码量能减少多少:

代码语言:javascript
复制
// 实体类
@Data
@TableName("product")
publicclass Product {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String name;
    private BigDecimal price;
    private Integer stock;
    private LocalDateTime createTime;
}

// Service接口
publicinterface ProductService extends IService<Product> {
}

// Service实现(按常规写法,也可以自动生成)
@Service
publicclass ProductServiceImpl extends ServiceImpl<ProductMapper, Product> implements ProductService {
}

// Controller——就这一行!
@RestController
@RequestMapping("/product")
publicclass ProductController extends BaseController<ProductService, Product> {
}

你没看错,创建一个完整CRUD的Controller只需要继承BaseController

增删改查、分页、列表……这些接口全部自动生成,一条代码都不用写!

四、底层原理深度剖析

很多小伙伴可能会好奇:BaseController里的这些方法是如何知道要对哪张表操作的呢?

为什么我写一个方法,对应的SQL就自动生成了?

这个问题的答案藏在MybatisPlus的底层执行链路里。

4.1 完整的SQL执行链路

我们用一张清晰的流程图来展示从Controller调用到数据库返回结果的全过程:

4.2 重点剖析:BaseMapper的注入机制

这里我来重点讲一下MybatisPlus的核心魔法是如何实现的。

很多小伙伴可能每天都在用BaseMapper,但从来没想过它是怎么生效的。

先回顾一下MyBatis的核心流程:MyBatis通过SqlSessionFactoryBuilder读取mybatis-config.xml配置文件,构建SqlSessionFactory,再由SqlSessionFactory创建SqlSession,每次操作数据库都通过SqlSession完成。

MyBatis底层自定义了Executor执行器接口来操作数据库,Executor有两个主要实现:一个是基本执行器(SimpleExecutor),一个是缓存执行器(CachingExecutor)。

MybatisPlus的强大之处在于它在这个基础上做了增强:

1. 动态代理机制:在Spring启动时,所有继承了BaseMapper的接口都会被MapperProxyFactory使用JDK动态代理创建代理对象。当你调用productMapper.selectById(1L)时,实际上调用的是MapperProxy的invoke方法,这个方法会拦截你的接口调用,分析出你要执行的是一个名为selectById的查询操作。

2. MappedStatement的自动生成:MybatisPlus的SQL注入器(SqlInjector)会在应用启动时,为BaseMapper中的所有通用方法生成对应的MappedStatement。

MappedStatement是MyBatis的底层封装对象,它包含了SQL模板、参数类型、返回结果类型等完整的SQL执行信息,mapper.xml文件中一个SQL操作就对应一个MappedStatement。

MP在启动时自动将这些SQL注入到MyBatis的Configuration中,这就是为什么我们不需要写任何XML就能执行SQL的原因。

3. 拦截器链(Interceptor Chain) :MybatisPlus通过MybatisPlusInterceptor构建了一个拦截器链,在Executor执行前注入各种增强逻辑,比如分页、乐观锁、多租户等,这些功能都是通过拦截器和责任链模式来实现的。

4. 完整的方法解析机制:当你的Mapper接口调用通用方法时,MybatisPlus能知道这是一个通用方法还是自定义方法,并找到对应的MappedStatement来执行SQL。

4.3 条件构造器(Wrapper)的工作原理

以条件查询为例,下面是条件构造器内部执行的详细流程:

Lambda表达式的核心优势是类型安全——使用User::getName而不是字符串"name",如果字段名写错了,编译器立刻就会报错,而传统的字符串写法只有到运行时才会发现SQL错误,这是技术上对“约定优于配置”理念的一种实现。

五、优缺点

5.1 优点

1. 开发效率呈指数级提升一个模块从接口定义到CRUD功能完整可用,只需要继承BaseController并注入Service,5行代码搞定。相比原生MyBatis要写XML、Mapper、Service、ServiceImpl、Controller,工作量减少80%以上。

2. 代码风格统一,降低维护成本所有模块的增删改查逻辑全部来自同一个BaseController,代码风格完全一致。新人接手项目时,不需要去研究每个Controller用了什么技巧——所有的结构和处理方式都一样。

3. 零XML配置MybatisPlus本身已经做到了不需要XML配置BaseCRUD,而Pro版进一步把Controller层的配置也省掉了。

4. 功能完备,开箱即用分页插件已经内置,只需要传pageNo和pageSize就可以获得完整的分页查询结果。条件构造器可以灵活构建复杂的查询条件,比如eq(等于)、like(模糊匹配)、between(范围查询)、orderBy(排序)等,可以满足绝大多数业务场景的需求。

5. 非侵入式设计MybatisPlus Pro是在MybatisPlus基础上做的增强,你仍然可以在需要复杂SQL的地方编写原生MyBatis的Mapper和XML,两者完全兼容。

5.2 缺点

1. 复杂多表关联查询仍然是痛点一旦涉及三张表以上的关联查询,条件构造器会让代码变得非常复杂和难以阅读。有经验的小伙伴应该能体会到:用Wrapper写三表关联,代码的逻辑可能比写SQL还要绕。原因在于Wrapper本身设计是用来构建单表查询条件的,它并不擅长处理多表JOIN的语义表达。

2. 部分场景存在性能隐患需要特别注意的是,BaseMapper自带的selectById、selectList等方法默认会查询所有字段——相当于SQL中的SELECT *。在MySQL性能优化中,有一条黄金法则:不要使用SELECT *,因为这会增加不必要的网络传输和内存开销,尤其是当表中包含TEXT、BLOB这类大字段时,性能问题更加明显。

官方在使用说明中也提醒:MP会对启动即自动注入基本CRUD操作,性能损耗虽然极小,但在极端场景下需要谨慎考量。

3. 一些方法存在魔法值隐患比如使用QueryWrapper<User>().eq("id", 1)这种方式,字段名是以字符串形式写死的。如果数据库字段名改了,编译阶段根本无法发现,只有运行到那一行代码时才会报错,排查起来非常耗时。这也是为什么官方越来越推崇LambdaWrapper的原因——用方法引用代替字符串,从根本上解决这个问题。

4. 部分功能有过度封装之嫌有开发者认为MP一定程度上“侵入”了Service层,当业务逻辑变得非常复杂时,这些封装好的通用方法反而可能成为限制。

5. SQL可读性和调优难度增加当使用复杂的条件构造器时,生成的SQL语句对调试来说不够直观。如果不熟悉MP的内部机制,定位慢查询的原因会比直接看XML配置的SQL更困难。

六、使用场景与选型建议

6.1 推荐使用MybatisPlus Pro的场景

场景1:中小型项目、快速迭代项目这类项目的特点是需求变化快,对开发速度要求高,但对极致性能要求相对较低。使用MybatisPlus Pro可以大幅提升开发效率。

场景2:后台管理系统、管理后台类应用这些应用的核心功能就是围绕数据的增删改查,模块数量多、业务逻辑相对简单。Pro版特别适合这类场景。

场景3:基础CRUD操作占主导的项目如果项目中80%以上都是单表增删改查操作(比如各种配置管理、字典管理、基础数据维护),Pro版能带来巨大的效率提升。

场景4:开发团队规模较小,希望统一代码风格小团队或者个人开发者,用Pro版可以快速搭建项目,同时保证代码结构清晰统一。

6.2 谨慎使用或不推荐的场景

场景1:业务核心、对SQL执行效率要求极高的系统比如交易系统、支付系统、高并发的订单系统等,需要对每一条SQL的性能做极致调优。MP的通用方法(尤其是SELECT *)可能会成为性能瓶颈。

场景2:复杂报表系统、复杂数据统计系统这类系统往往需要编写十几行甚至几十行的复杂SQL,多表关联、子查询、聚合函数比比皆是。这种情况下,直接编写原生SQL比使用条件构造器更清晰、更可控。

场景3:对SQL需要精细审核的安全敏感系统在一些金融、政务等对SQL语句需要逐条审查的场景下,框架自动生成的SQL可能不太符合审核要求。此时建议全部使用手写的XML SQL。

6.3 架构选型时的对比参考

下面这张表帮大家快速理清MyBatis、MybatisPlus和MybatisPlus Pro之间的差异,做到“选型有据”:

维度

原生MyBatis

MybatisPlus

MybatisPlus Pro

CRUD开发速度

慢(需手写SQL)

快(BaseMapper)

极快(全自动)

代码量

较少

很少

多表SQL灵活性

高(完全可控)

高(兼容原生)

高(兼容原生)

学习成本

中等

中等

定制化程度

中高

统一代码风格

一般

适用项目

所有

所有

中大型+快速开发

对数据库的掌控力

完全掌控

单表可封装

单表可封装

这个对比表帮大家在技术选型时做到心里有数——每个框架都有自己的优势和局限,关键是根据项目的实际情况来做选择。

七、避坑指南

作为过来人,我踩过不少坑,下面分享几点实战经验:

坑1:不要过度使用通用查询方法

有小伙伴在工作中喜欢“一个selectList走天下”,这样虽然方便,但遇到高频查询的接口时,SELECT *查询所有字段会严重影响性能。建议重要的查询接口一定要用select()指定字段。

坑2:Lambda版本优先于字符串版本

使用LambdaQueryWrapper代替QueryWrapper,通过方法引用的方式避免硬编码字符串,既保证了类型安全,又避免了字段名变更时的隐式错误。

代码语言:javascript
复制
// ✅ 推荐写法(类型安全)
userMapper.selectList(Wrappers.<User>lambdaQuery()
    .eq(User::getStatus, 1)
    .like(User::getUsername, "zhang")
    .orderByDesc(User::getCreateTime));

// ❌ 不推荐写法(字符串容易拼错)
userMapper.selectList(Wrappers.<User>query()
    .eq("status", 1)
    .like("username", "zhang"));

坑3:分页一定要用MybatisPlus的分页插件

不要自己写LIMIT,MybatisPlus内置的分页插件基于物理分页,对不同数据库提供了良好的兼容性,不仅代码简洁,性能也好很多。

坑4:复杂SQL果断放弃Wrapper,回归原生

当查询涉及三张表以上的关联时,我的建议是:不要挣扎,直接在Mapper XML里写SQL。清晰、可控、易于调优,这才是真正的生产力。

坑5:实体字段命名遵循驼峰规范

MP的自动映射依赖驼峰命名规范,建议始终使用标准的驼峰命名,避免不必要的配置。

总结

写到这里,我们来简单总结一下。

MybatisPlus Pro的出现,核心目标是解决一个根本问题:把开发者从海量的重复代码中解放出来。

它继承了MybatisPlus无侵入、高效率的设计理念,通过BaseController这一层巧妙的设计,把Controller层、Service层的重复劳动全部自动化。

配合工具类的反射机制和Wrapper条件构造器,还可以实现基于实体非空字段的自动条件查询——开发体验可以说是质的飞跃。

当然,没有银弹

Pro版在复杂多表关联、极致性能优化等场景下确实存在短板,但正如业界公认的:它解决了80%的简单CRUD重复工作,剩下的20%复杂场景依然能手写SQL,两者结合才是最佳实践。

MyBatis-Plus在MyBatis的基础上只做增强不做改变,既简化了开发,又保留了MyBatis的所有灵活性。

最后,用一句话与所有开发者共勉:提高效率不是偷懒,而是把精力投入到更有价值的业务中去。

如果觉得有用,点个在看支持一下!

  • MybatisPlus 官方文档:https://baomidou.com
  • MybatisPlusPro 项目地址:https://gitee.com/nirui-gitee/mybatis-plus-pro
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2026-06-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 苏三说技术 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 一、为什么我们需要MybatisPlus Pro?
  • 二、MybatisPlus Pro是什么?
  • 三、快速上手
    • 第一步:引入依赖
    • 第二步:编写工具类
    • 第三步:编写通用BaseController
    • 第四步:实际使用
  • 四、底层原理深度剖析
    • 4.1 完整的SQL执行链路
    • 4.2 重点剖析:BaseMapper的注入机制
    • 4.3 条件构造器(Wrapper)的工作原理
  • 五、优缺点
    • 5.1 优点
    • 5.2 缺点
  • 六、使用场景与选型建议
    • 6.1 推荐使用MybatisPlus Pro的场景
    • 6.2 谨慎使用或不推荐的场景
    • 6.3 架构选型时的对比参考
  • 七、避坑指南
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档