☰
MyBatis-Plus配置避坑指南:从分页失效到全局策略的深度解析
2026/10/14 12:54:27 网站建设 项目流程

1. 从一次线上分页失效说起:为什么配置项不能只靠默认值

前阵子帮一个朋友排查问题,他们项目用的是 MyBatis-Plus,本地跑得好好的,一上测试环境分页就失效了,查出来的数据永远是全量。日志里 SQL 也没带 limit,但代码里明明调用了Page对象。折腾了大半天,最后发现是有人为了“优化启动速度”,在配置类里手动 new 了一个MybatisPlusInterceptor,却忘了把分页插件加进去。这件事让我意识到,MyBatis-Plus 的 configuration、globalConfig、dbConfig 这三块配置,看着简单,实际上每一个字段背后都对应着一类容易踩的坑。

这篇内容我打算把 MyBatis-Plus 的配置体系从头到尾捋一遍。核心围绕三块:configuration里那些直接影响运行时行为的开关、globalConfig里控制全局默认策略的字段、以及dbConfig中跟日志和主键策略相关的设置。适合正在用 MyBatis-Plus 做项目、但配置基本靠复制粘贴的开发者,也适合想搞清楚“为什么我的配置不生效”的排查者。我会尽量把每个配置项背后的逻辑讲透,而不是只列一个字段表。

需要先说明一点:MyBatis-Plus 的配置入口在不同版本里略有差异。3.x 早期版本大量配置挂在GlobalConfig和MybatisConfiguration上,而 3.4.0 之后官方推荐用MybatisPlusProperties配合MybatisPlusInterceptor来组织。下面讲的内容以 3.5.x 为主线,涉及版本差异的地方我会单独标出来。你如果用的是老版本,看到字段名对不上不用慌,逻辑是相通的。

2. configuration 配置:那些直接改变 SQL 执行行为的开关

configuration这一层对应的是 MyBatis 原生的org.apache.ibatis.session.Configuration,MyBatis-Plus 在它基础上做了扩展。很多人以为这层配置只是“调调缓存、改改日志”,实际上它里面好几个字段直接决定了你的 SQL 会不会按预期执行。我把它拆成三类来看:影响映射行为的、影响执行性能的、影响结果处理的。

2.1 mapUnderscoreToCamelCase:最容易被忽略却最常出问题的字段

这个字段默认值是true,意思是数据库的user_name会自动映射到 Java 对象的userName。听起来很美好,但坑就坑在“默认开启”这件事上。我见过一个项目,数据库字段叫user_name,实体类属性也叫user_name(对,就是下划线风格),结果查询出来这个字段永远是 null。原因就是自动驼峰转换把user_name转成了userName去匹配,而实体里根本没有userName这个属性。

正确的做法是:要么统一用驼峰命名实体属性,要么在实体字段上显式加@TableField("user_name")注解。如果你确实想关掉自动转换,就在配置里写:

mybatis-plus: configuration: map-underscore-to-camel-case: false

但我要提醒一句,关掉之后所有下划线字段都得手动映射,工作量不小。所以更推荐的做法是保持开启,然后在实体命名上统一规范。这里有个经验:新项目立项时就把命名规范定死,数据库用下划线、Java 用驼峰,中间靠自动转换衔接,别混着来。

2.2 cacheEnabled 与二级缓存:什么时候该开,什么时候必须关

cacheEnabled默认是true,控制的是 MyBatis 的二级缓存开关。注意,它只是“总开关”,真正要让某个 Mapper 用上二级缓存,还得在 Mapper XML 里加<cache/>标签。很多分布式项目里二级缓存是灾难的来源,因为它是 JVM 级别的,多节点部署时各节点缓存不一致,读到的数据可能是脏的。

我的建议很直接:如果你的项目是多实例部署,或者对数据一致性要求高,直接在配置里关掉:

mybatis-plus: configuration: cache-enabled: false

单机部署、读多写少、数据变化不频繁的场景,可以开。但即便开,也要配合flushCache策略,确保写操作后缓存及时失效。这里没有银弹,得根据业务读写比来权衡。

2.3 defaultExecutorType:批处理场景下的性能分水岭

defaultExecutorType有三个可选值:SIMPLE、REUSE、BATCH。默认是SIMPLE,每次执行都新建 Statement。REUSE会复用 Statement,BATCH则支持批量执行。

这个字段在批量插入场景下差别巨大。我之前做过一个测试,插入一万条数据,用SIMPLE逐条插入花了将近 40 秒,换成BATCH之后降到 3 秒出头。配置方式:

mybatis-plus: configuration: default-executor-type: batch

但要注意,BATCH模式下insert返回的影响行数可能不准,而且事务提交前数据不一定落库。所以它适合大批量导入、日志写入这类场景,不适合需要立即拿到自增主键的业务。另外,如果你只是偶尔批量操作,更推荐用SqlSessionFactory手动开一个 batch session,而不是全局改执行器类型,避免影响其他正常业务。

2.4 logImpl 与日志实现的选择逻辑

logImpl指定 MyBatis 用哪个日志框架输出 SQL。可选值有SLF4J、LOG4J、LOG4J2、JDK_LOGGING、COMMONS_LOGGING、STDOUT_LOGGING、NO_LOGGING。默认情况下 MyBatis 会按顺序自动查找,找到哪个用哪个。

这里有个常见的困惑:为什么我配了logging.level.xxx=debug,SQL 还是不打印?因为 MyBatis 的日志输出走的是它自己的logImpl,跟你 Spring Boot 的日志级别是两套体系。稳妥的做法是显式指定:

mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl

然后在logging.level里把对应 Mapper 包的级别调到 debug。这样 SQL、参数、结果集都能打出来。生产环境记得关掉,或者只对特定 Mapper 开,不然日志量会爆炸。

3. globalConfig 全局配置:默认策略的集中管控区

globalConfig是 MyBatis-Plus 特有的,它管的是“全局默认行为”。什么意思呢?就是当你没有在实体或 Mapper 上单独指定时,框架按这里的配置来。它和configuration的区别在于:configuration更偏底层执行,globalConfig更偏上层策略。我重点讲三个最常打交道的字段。

3.1 dbConfig.idType:主键生成策略的全局默认值

idType决定了插入时主键怎么生成。默认是ASSIGN_ID,也就是雪花算法。这个默认值在 3.3.0 之后改过,老版本默认是AUTO(数据库自增)。这个变化坑了不少升级的项目:升级之后发现插入报错,因为数据库表的主键是自增的,但框架按雪花算法生成了一个长整型 ID 塞进去,类型对不上。

配置方式:

mybatis-plus: global-config: db-config: id-type: auto

可选值我列个表对比一下:

idType 值生成方式适用场景注意事项
AUTO数据库自增单库单表,主键为自增列依赖数据库,分库分表下不适用
ASSIGN_ID雪花算法分布式、分库分表生成 Long 型,前端要注意精度丢失
ASSIGN_UUIDUUID需要字符串主键无序,索引性能差
INPUT手动输入业务自定义主键必须自己赋值,否则报错
NONE无策略跟随全局一般不用

这里有个细节:ASSIGN_ID生成的 ID 是 19 位 Long,前端 JavaScript 的 Number 精度只有 16 位,直接返回会导致末几位变成 0。解决办法是后端序列化成 String,或者前端用字符串接收。这个坑我在至少三个项目里见过。

3.2 dbConfig.tablePrefix 与 schema:多模块项目的命名隔离

tablePrefix用于给所有表名统一加前缀。比如你配置了table-prefix: t_,那实体User默认就映射到t_user表。这在多模块共用一个库、需要按模块加前缀的场景下很有用。

mybatis-plus: global-config: db-config: table-prefix: t_ schema: mydb

schema则是指定默认的库名,适合多 schema 的场景。但要注意,一旦配了tablePrefix,所有实体都会受影响,如果某个表没有这个前缀,就得在@TableName里显式写全名覆盖。所以我的经验是:前缀要么全项目统一,要么就别用,半用不用最容易乱。

3.3 logicDeleteField 与逻辑删除的全局约定

逻辑删除是 MyBatis-Plus 的一个亮点功能,配置好之后,delete操作会自动变成update set deleted=1,查询自动带上deleted=0。全局配置如下:

mybatis-plus: global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0

配好之后,实体里加一个deleted字段,加@TableLogic注解即可。但这里有个大坑:逻辑删除对唯一索引不友好。比如你有个username唯一索引,删掉一个用户后再注册同名用户,会因为唯一索引冲突失败,因为旧记录还在,只是deleted=1。解决办法是把唯一索引改成(username, deleted)联合唯一,或者删除时把username改成一个带时间戳的值。这个坑不提前想好,上线后很难改。

4. dbConfig 日志配置:让 SQL 输出真正可控可读

日志这块我单独拎出来讲,因为它太重要了。线上排查问题,第一手资料就是 SQL 日志。但很多人配的日志要么打不全,要么打太多,要么格式乱得没法看。MyBatis-Plus 的日志配置其实分两层:一层是configuration.logImpl决定用哪个日志框架,另一层是dbConfig里跟 SQL 打印相关的设置。

4.1 标准输出与 SLF4J 的取舍

开发阶段图省事,很多人直接配STDOUT_LOGGING,SQL 直接打到控制台。但这种方式没法跟项目的日志体系整合,也没法按级别过滤。我的建议是统一走 SLF4J:

mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl logging: level: com.example.mapper: debug

这样 SQL 会以 debug 级别输出,生产环境把级别调到 info 就自动不打了,不用改代码。而且能跟 Logback、Log4j2 的 appender 配合,输出到文件、按天切割都方便。

4.2 打印 SQL 时如何同时看到参数和结果

默认情况下,MyBatis 打印的 SQL 是带?占位符的,参数在下一行单独打。排查问题时经常要手动把参数填进去,很麻烦。有个小技巧是配合p6spy这类工具,它能直接输出可执行的完整 SQL。但引入额外依赖要谨慎,我一般只在排查特定问题时临时加。

另一个办法是调日志级别。MyBatis 的BaseJdbcLogger在 debug 级别下会打印参数和结果集,trace 级别下会打印更多细节。所以如果你只看到 SQL 看不到参数,检查一下是不是级别只开到了 info。

4.3 生产环境日志的裁剪策略

生产环境最怕的就是 SQL 日志把磁盘打满。我的做法是:默认不开 SQL 日志,只在需要排查的 Mapper 上单独开。比如:

logging: level: root: info com.example.mapper.OrderMapper: debug

这样只有订单相关的 SQL 会打出来,其他保持安静。另外,慢 SQL 的监控不要依赖 MyBatis 日志,应该用数据库层面的慢查询日志或者 APM 工具,更准确也更省资源。

5. 配置不生效的排查链路:从加载顺序到覆盖规则

配置写对了但不生效,这是最让人抓狂的。我总结了一套排查顺序,基本能覆盖九成以上的问题。

5.1 先确认配置到底有没有被加载

第一步永远是确认配置有没有进到Configuration对象里。最直接的办法是在启动类里打个断点,或者写个CommandLineRunner把SqlSessionFactory的Configuration打出来看看。重点看mapUnderscoreToCamelCase、logImpl这些字段的值是不是你配的。

如果发现没生效,先检查配置文件的位置和格式。application.yml里mybatis-plus下面的层级不能错,configuration和global-config是平级的,别嵌套错了。YAML 对缩进敏感,多一个空格少一个空格结果完全不同。

5.2 多来源配置的覆盖顺序

MyBatis-Plus 的配置可能来自多个地方:application.yml、Java 配置类里的@Bean、还有MybatisPlusPropertiesCustomizer。它们的覆盖顺序是:Java 代码里的设置优先级高于配置文件。也就是说,如果你在配置类里手动 new 了一个GlobalConfig并 set 了某个值,那 yml 里配的就会被覆盖。

我踩过的一个坑就是:项目里有个老的配置类,手动设置了GlobalConfig,后来新同事在 yml 里改了idType,怎么都不生效。最后发现是配置类里的旧代码在“作祟”。所以排查时一定要全局搜一下GlobalConfig、MybatisConfiguration这些类的实例化代码。

5.3 版本升级带来的默认值变化

前面提过idType默认值从AUTO变成ASSIGN_ID,类似的还有logicDeleteField的默认行为调整。升级 MyBatis-Plus 版本时,一定要看官方 changelog 里关于默认值的改动。我的习惯是:升级前先把当前所有依赖默认值的配置项显式写出来,升级后再逐个确认,避免“默认值变了但代码没改”导致的隐性 bug。

6. 一套可直接抄的配置模板与我的实操心得

讲了这么多原理,最后给一份我目前在用的配置模板,基于 Spring Boot + MyBatis-Plus 3.5.x,你可以按需裁剪。

mybatis-plus: configuration: map-underscore-to-camel-case: true cache-enabled: false log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl default-executor-type: simple global-config: banner: false db-config: id-type: assign_id logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0 table-prefix: t_ mapper-locations: classpath*:/mapper/**/*.xml type-aliases-package: com.example.entity logging: level: com.example.mapper: debug

几个我反复验证过的经验点。第一,banner: false建议加上,不然每次启动都打一堆 MyBatis-Plus 的 logo,日志里很碍眼。第二,mapper-locations用classpath*:而不是classpath:,前者能扫描到所有 jar 包里的 mapper 文件,多模块项目下更稳。第三,type-aliases-package配了之后 XML 里写 resultType 就不用写全限定名了,但要注意包路径别配太宽,否则启动时扫描类会变慢。

还有一个关于分页插件的提醒:分页功能不是靠 configuration 配出来的,而是要在配置类里注册MybatisPlusInterceptor并添加PaginationInnerInterceptor。这个拦截器不注册,Page对象就是个摆设。注册时记得指定数据库类型:

@Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; }

指定DbType是为了让分页插件生成对应数据库的方言 SQL,不指定的话它会自己去猜,多数据源场景下容易猜错。

最后说个我自己的习惯:每次新建项目,我会先把这份配置模板贴进去,然后根据业务逐项调整,而不是等出了问题再回头补。配置这东西,前期花十分钟想清楚,后期能省十个小时的排查时间。尤其是idType和逻辑删除这两块,一旦数据写进去了再改,成本极高。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询