Mybatis-Plus和Mybatis-Flex,哪个更好?

0 阅读14分钟

前言

最近在技术群里,有个小伙伴问我:“三哥,我们新项目要做技术选型,Mybatis-Plus和Mybatis-Flex选哪个啊?”

说实话,这个问题真的问到了很多人的痛点。

一个号称“Simplify development”、“只做增强,不做改变”,已经成为国内Java开发的事实标准。

另一个则宣称“比Mybatis-Plus快5~10倍”、“原生支持复杂关联查询”,来势汹汹、野心勃勃。

到底谁更好?

这篇文章专门跟大家一起聊聊这个话题,希望对你会有所帮助。

更多项目实战在我的技术网站:susan.net.cn/project

Mybatis-Plus:站在巨人的肩膀上

它是一个什么样的框架?

Mybatis-Plus是对MyBatis的增强工具,在MyBatis的基础上只做增强不做改变,它完全兼容MyBatis的所有特性,同时提供大量开箱即用的功能。

我们先看一个小白都能秒懂的对比:如果你用原生MyBatis,写一个简单的插入操作,你需要写Mapper接口、XML映射文件、实体类,少说几十行代码。

而Mybatis-Plus只需要定义实体类并继承BaseMapper,然后直接userMapper.insert(user)就搞定了,代码量减少60%以上。

底层原理:它到底是怎么增强的?

要理解Mybatis-Plus的本质,我们得从MyBatis的底层执行链路说起。

MyBatis的核心执行链路:调用mapper.selectById(1L) → MapperProxy.invoke() → MapperMethod.execute() → DefaultSqlSession.selectOne() → CachingExecutor.query() → SimpleExecutor.doQuery() → StatementHandler.prepare() → ParameterHandler.setParameters() → ResultSetHandler.handleResultSets()。

这张图展现了MyBatis从接口调用到数据库执行的完整链路:

image.png

Mybatis-Plus是在这条链路中,利用MyBatis的插件(Interceptor)机制,在关键环节插入自定义逻辑来实现增强的。

它通过MybatisPlusInterceptor将功能拆分为责任链(PaginationInnerInterceptorOptimisticLockerInnerInterceptorTenantLineInnerInterceptor等),每个拦截器只处理一个横切关注点。

核心工作原理分三步走:

  1. 启动时扫描与注入:在Spring Boot自动配置阶段,MybatisPlusAutoConfiguration会扫描所有继承BaseMapper的Mapper接口,为每个接口动态生成一个代理类,并把通用的CRUD SQL注入到MyBatis的MappedStatement集合中。这个过程依赖SQL注入器(MybatisSqlInjector),它会解析BaseMapper中定义的每个方法,生成对应的SQL语句并注册到MyBatis中。
  2. 方法调用时的动态代理:当你调用baseMapper.insert(entity)时,MapperProxy代理会拦截这个调用。Mybatis-Plus会根据实体类的字段配置(如主键策略@TableId、字段名@TableField等),动态拼装出完整的INSERT SQL语句,再交给MyBatis执行。
  3. 条件构造器的SQL拼接:通过AbstractWrapper及其子类(QueryWrapperUpdateWrapper),将链式调用转换为参数化的SQL片段。Lambda方式通过SerializedLambda解析字段名,完全避免了硬编码字符串。

Plugin与Interceptor体系:插件类型包括Executor(SQL执行全阶段拦截)、StatementHandler(SQL语句构建阶段拦截)、ParameterHandler(参数设置阶段拦截)和ResultSetHandler(结果处理阶段拦截)。

核心特性深度剖析

CRUD操作的智能化生成

MyBatis-Plus最核心的功能就是零SQL的CRUD操作。

开发者只需让自己的Mapper接口继承BaseMapper接口,即可立即获得数十个常用的数据操作方法,无需编写任何XML或SQL语句。

// 只需继承BaseMapper,就能拥有所有单表CRUD能力
public interface UserMapper extends BaseMapper<User> {
    // 这里什么都不用写!
}

// 使用示例
User user = userMapper.selectById(1L);  // 根据ID查询
List<User> users = userMapper.selectList(wrapper);  // 条件查询
userMapper.insert(user);  // 插入
userMapper.updateById(user);  // 更新
userMapper.deleteById(1L);  // 删除

Lambda条件构造器

Mybatis-Plus的LambdaQueryWrapper提供了类型安全的条件构造:

LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(User::getName, "张三")
       .gt(User::getAge, 18)
       .orderByDesc(User::getCreateTime);
List<User> users = userMapper.selectList(wrapper);

相较于字符串形式的字段名,Lambda表达式在编译期即可检查字段是否存在,避免了因字段名拼写错误导致的运行时异常。

分页插件(物理分页)

PaginationInnerInterceptor通过拦截器机制实现了物理分页,避免了内存分页的性能问题。

配置后,只需在方法参数中传入Page<User>对象,即可自动生成LIMIT分页SQL:

@GetMapping("/list")
public IPage<User> list(Page<User> page) {
    return userService.page(page); 
    // 自动生成:SELECT * FROM user LIMIT (page-1)*size, size
}

乐观锁插件

通过@Version注解实现版本号控制,防止并发更新导致的数据不一致。

更新操作会自动校验版本号,若版本不匹配则抛出异常。

// 实体类中标记版本字段
@Version
private Integer version;

Mybatis-Plus的最大痛点

依赖繁重是Mybatis-Plus最需要警惕的问题。

它的核心CRUD通过动态代理和反射实现,这意味着每一个INSERT、UPDATE、DELETE操作,框架都要先用反射扫描一遍实体类的所有注解和字段,才能组装出完整的SQL。

对于一些数据量巨大的批量业务,这种动态解析的开销可能会被急剧放大。

在分库分表、极度追求执行效率的高并发场景下,可能会成为压垮骆驼的最后一根稻草。

image.png

Mybatis-Flex:新生代的挑战者

设计哲学—“效能优化”而非“功能堆砌”

MyBatis-Flex的出现标志着增强框架从“功能堆砌”向“效能优化”的转变。

它是MyBatis官方社区衍生出的增强版本,基于MyBatis核心,新增了大量灵活的动态SQL构建能力、丰富的实体策略、类型安全的链式语法。

最核心的设计决策是:整个框架除了MyBatis本身,再无任何第三方依赖!在任何一个系统中,依赖越多,稳定性越差。

底层原理:避开拦截器的性能陷阱

MyBatis-Flex为什么能做到“比MyBatis-Plus快5~10倍”?

核心奥秘在于它完全避开了MyBatis的拦截器(Interceptor)机制,也没有运行时反射和SQL解析开销

它的查询构建过程不是通过MyBatis的拦截器动态修改SQL,而是基于SqlProvider技术架构。

在编写Java查询代码时,它的QueryWrapper会直接构建出一个最终可执行的SQL字符串,然后直接传递给MyBatis去执行,中间没有任何额外的SQL解析步骤。

图片

这张图揭示了MyBatis-Flex最大胆的一点:它把SQL构建过程前置到了应用层的Java代码里,几乎没有对MyBatis的运行内核做任何侵入,把易用性和运行性能的冲突降到了最低。

另一个关键设计是APT(Annotation Processing Tool)编译期代码生成

在项目编译阶段,APT处理器就会扫描所有标注了@Table的实体类,并为之生成一套专门的“列名映射常量类”及辅助代码。

在运行时,框架直接读取这些编译好的类,彻底避开了运行时的反射和损耗。

多表联查:原生支持的降维打击

在Mybatis-Plus中实现多表查询通常有两种方式:要么回到XML里手写SQL,要么使用并不完善的联表扩展(Mybatis-Plus部分高级功能需要付费)。

而MyBatis-Flex在框架层就将复杂联查支持作为一等功能来设计:

QueryWrapper query = QueryWrapper.create()
    .select(ACCOUNT.ID, ACCOUNT.USER_NAME, ORDER.AMOUNT.sum())
    .from(ACCOUNT)
    .leftJoin(ORDER).on(ACCOUNT.ID.eq(ORDER.USER_ID))
    .groupBy(ACCOUNT.ID);
List<AccountVO> results = accountMapper.selectListByQuery(query);

这种类SQL的链式API设计,让开发者在编写联表查询时,既能享受到IDE代码补全的类型安全,又不需要切换上下文。

动态表名与字段的极致灵活性

在一些多租户或者按时间分表的业务场景中,需要根据上下文动态拼接表名。

在Mybatis-Plus中要实现这点非常麻烦,通常不得不放弃BaseMapper的优雅API,回到笨重的XML里写动态SQL。

// MyBatis-Flex一行代码即可完成动态表名更新
Db.update("book_store_" + orgCode)
    .set("source_code", clashVal)
    .where("content_id = ?", channelId);

不用XML,不用注解拼接,像写Java一样优雅。

无实体类操作的Db+Row工具

MyBatis-Flex提供了Db+Row工具,可以在不需要定义实体类的情况下,直接对数据库进行增删改查以及分页查询。

这在做一些快速开发、原型验证或者报表类项目时非常方便。

八大维度全方位对比

既然两个框架各有特色,那我们就要从多个维度客观、公正地做一个全方位的硬核对比。

在以下8个维度的分析中,大家可以对号入座,找到最适合自己项目的方案。

一、核心设计哲学与底层原理

  • Mybatis-Plus:MyBatis的无侵入增强,利用MyBatis的拦截器机制实现功能增强,启动时通过动态代理生成SQL并注入。核心依赖Spring、MyBatis等。运行时反射解析SQL和条件构造器,拦截器链会对SQL进行多次拦截和改写。
  • Mybatis-Flex:只做增强不做改变,完全基于MyBatis核心。采用编译期APT生成列映射常量,直接构建完整SQL字符串后交给MyBatis执行。仅依赖MyBatis,无其他第三方依赖,整个生命周期完全没有MyBatis拦截器介入和运行时SQL解析,性能上限极高。

二、开发效率与上手难度

  • Mybatis-Plus:集成极其简单,生态成熟。内置BaseMapper提供几十个CRUD方法,代码生成器可一键生成全套代码。Lambda条件构造器优雅好用。国内使用者基数庞大,中文资料极其丰富。对于新手极其友好,几乎开箱即用。适合追求快速上手的业务系统。
  • Mybatis-Flex:上手较快,同样支持BaseMapper继承和链式查询。使用QueryWrapper时思路与MP类似,移植成本不高。但中文资料相对较少,遇到偏僻问题可能需要钻研源码。适合熟悉SQL、追求极致控制和编码体验的开发者。

三、查询灵活性(核心胜负手)

  • Mybatis-Plus:Wrapper体系对单表查询支持非常优雅,支持lambda类型安全。但在多表关联、子查询等复杂场景下,Wrapper体系的表达能力不足。多表join查询要么回到XML写SQL,要么依赖只支持简单join的第三方工具。联表查询能力相对较弱。
  • Mybatis-Flex:QueryWrapper原生支持复杂的join关联查询,支持left join, right join, inner join,union all和子查询。支持超110个SQL函数。支持动态表名、动态Schema等场景。在复杂查询上拥有碾压级优势。

四、性能表现

根据权威基准测试数据(基于查询10000条记录的场景):

  • Mybatis-Plus:性能中等。因为Mybatis-Plus的CRUD和条件构造器是通过拦截器和运行时反射动态拼接SQL实现的,每次操作都有一定的运行时解析开销。批量操作和复杂查询场景下性能损耗相对更明显。
  • Mybatis-Flex:查询性能领先5~10倍,拥有接近原生MyBatis的极限性能。由于没有拦截器介入,运行时无SQL解析,且APT消除了不必要的运行时反射。对于极其简单的单条CRUD操作,二者差距不大,但在批量、多表、大数据量场景下,MyBatis-Flex优势极为明显。

五、多数据库与方言支持

  • Mybatis-Plus:主流数据库都有良好支持,例如MySQL, PostgreSQL, Oracle, SQL Server等。但对国产数据库的支持相对较慢。
  • Mybatis-Flex:除了MySQL、Oracle等主流库,还内置支持包括达梦、人大金仓、华为高斯等20多种国产数据库方言。在信创国产化趋势下非常有竞争力。

六、高级特性完备度

  • Mybatis-Plus:主键策略多样化、逻辑删除、乐观锁、多租户(TenantLineInnerInterceptor)、数据权限、自动填充功能、代码生成器等生态非常完善。但部分高级功能(如多表关联、二级缓存)需要付费或依赖社区提供的商业插件。
  • Mybatis-Flex:支持多主键、逻辑删除(多种删除策略)、乐观锁、数据填充、数据脱敏、多租户等。全部功能完全免费开源。还有Db+Row无实体类操作等特色工具。

七、依赖粒度与侵入性

  • Mybatis-Plus:相对较重。引入了spring-tx、mybatis-spring等,同时还依赖多个第三方库。
  • Mybatis-Flex:极致轻量化。除了MyBatis本身,没有任何第三方依赖。在微服务架构中,可以减少Jar包冲突的风险,更轻盈。

八、社区生态与未来前景

  • Mybatis-Plus:GitHub 15k+ Stars,国内企业中应用极广。中文社区非常活跃,问题解决快,贡献者多。版本迭代稳定,依然是当前Java持久层的事实标准。
  • Mybatis-Flex:较新的框架,开发者活跃度很高。Changelog显示近期持续迭代,已支持Spring Boot 4,并引入了JSpecify空值注解等现代化改进。发展趋势非常迅猛。

总结

工作中如何选型?

无脑选Mybatis-Plus的情况:

  1. 传统企业级项目:需要稳定、成熟、经过大规模生产验证的解决方案。MP已经拥有数年的大规模应用考验,稳定性和社区资源都非常完善。
  2. 极致的快速开发场景:项目几乎是标准的单表CRUD业务,对多表联查的需求极少。MP的开箱即用能极大提升开发效率。
  3. 你的团队已经深度使用MP:已经有稳定的开发模板和技术积累。在这种情况下,全面换用Flex的边际收益可能不值得投入额外的学习成本。
  4. 依赖代码生成器完成全套脚手架搭建的项目:MP的代码生成器覆盖了Controller、Service、Entity等全部层次,适合快速初始化项目。
  5. 老旧MyBatis项目升级平滑过渡:MP在这一点上非常成熟,几乎零成本替换原生MyBatis。

果断转MyBatis-Flex的情况:

  1. 复杂报表、高数据量的查询分析系统:你的业务需要频繁编写复杂的多表联查SQL。MyBatis-Flex原生、类型安全的联表支持,能极大提升编码效率和SQL的可维护性。
  2. 高并发、性能敏感的微服务:核心交易链路对单次请求的响应延迟要求极高,希望尽可能压低框架层的消耗。Flex的无SQL解析架构和APT编译期常量生成,会是极限压榨性能的大杀器。
  3. 多租户或动态Schema的场景:需要根据组织或时间动态切换表名甚至数据库。Flex原生且优雅的动态表名支持,能让你的代码看起来更清爽。
  4. 信创或国产化数据库适配项目:要适配达梦、人大金仓等国产数据库。Flex自带强大的方言支持更省心。
  5. 追求技术简洁和轻量化的团队:极度反感臃肿的依赖关系,希望框架粒度做到最小化,追求长期的可控性。

我的一些建议

技术选型从来不是非黑即白的零和博弈,它是平衡艺术和工程智慧的体现。

对于大部分以业务开发为主要工作、追求快速迭代上线、大部分场景都是单表操作的传统公司来说,Mybatis-Plus目前依然会是主流的选择。

它的生态完整性、社区成熟度和职场招聘的认知度,都是肉眼可见的加分项。

但如果你所在团队的架构组有比较强的控制欲,喜欢极致压榨服务器性能,相信“使用简单就是架构复杂”,那么MyBatis-Flex那种“编译期做复杂的事,运行时做极致简单的事”的设计哲学,确实更值得敬佩。

它在多表联查、动态表名等场景下,用更优雅的方式解决了让很多MP使用者难堪的痛点。

希望这篇文章,能帮你拨开迷雾,做出最适合当前项目的技术决策。

更多项目实战在我的技术网站:susan.net.cn/project