MyBatis-Plus三大配置块详解:Configuration、GlobalConfig与DbConfig避坑指南 1. 从一次线上事故说起为什么配置项值得单独拎出来讲去年帮一个朋友排查线上问题现象很诡异同一个查询接口测试环境返回的createTime是正常的2024-03-15 10:23:45到了生产环境却变成了2024-03-15T10:23:45前端直接解析失败页面上一片Invalid Date。查了半天代码逻辑一模一样最后定位到问题出在MyBatis-Plus的globalConfig里——两个环境的DbConfig配置不一致一个开了JSR310时间类型适配一个没开。这件事让我意识到很多人用 MyBatis-Plus 都是“能跑就行”Configuration、GlobalConfig、DbConfig这三块配置基本靠复制粘贴从来没认真看过每个字段到底管什么。等到出了玄学问题才发现根子全在这几个配置类里。这篇内容就是把这三大配置块彻底拆开讲清楚Configuration管的是 MyBatis 原生那套核心行为GlobalConfig是 MyBatis-Plus 自己加的全局策略DbConfig则专门负责数据库层面的日志、主键、表前缀这些事。搞明白它们各自管什么、怎么配、配错了会怎样你就能避开我踩过的那些坑。适合已经用过 MyBatis-Plus 但配置全靠抄的开发者也适合想从 MyBatis 迁移过来、需要搞清楚配置差异的朋友。2. Configuration 配置MyBatis 原生能力的总开关2.1 Configuration 到底是个什么东西Configuration是 MyBatis 的核心配置类你可以把它理解成整个 MyBatis 运行时的“大脑”。它持有MapperRegistryMapper 注册表、TypeHandlerRegistry类型处理器注册表、MappedStatement集合每条 SQL 的完整描述、InterceptorChain插件链等等。MyBatis-Plus 并没有另起炉灶而是在这个Configuration基础上做了增强所以你在 MyBatis-Plus 里配的Configuration本质上还是 MyBatis 那套东西。在 Spring Boot 环境下你通常不会直接 new 一个Configuration而是通过MybatisPlusProperties里的configuration字段来配置最终由MybatisPlusAutoConfiguration把它注入到SqlSessionFactoryBean里。理解这一点很关键你配的每一项最终都会反映到那个全局唯一的Configuration对象上。2.2 最容易被忽略的 mapUnderscoreToCamelCase这个配置项默认值是trueMyBatis-Plus 默认开启原生 MyBatis 默认是false作用是自动把数据库的user_name映射到 Java 的userName。看起来很方便但它有个坑一旦你手动写了resultMap这个自动映射就会失效因为resultMap是显式映射优先级高于自动映射。我见过有同学在实体类上加了TableField(user_name)又在 XML 里写了resultMap结果字段死活映射不上查了半天才发现是两套映射规则打架。我的建议是要么全用注解 自动映射要么全用resultMap显式映射别混着来。mybatis-plus: configuration: map-underscore-to-camel-case: true2.3 cacheEnabled 与二级缓存的取舍cacheEnabled默认true控制的是二级缓存的开关。注意它只是“总开关”真正要用二级缓存还得在 Mapper XML 里加cache/标签。很多人以为开了这个就能缓存结果发现每次还是查库就是因为没加cache/。但我要泼盆冷水在分布式环境下MyBatis 的二级缓存基本是个坑。它是基于PerpetualCache的本地缓存多节点部署时各节点缓存不一致更新操作容易读到脏数据。我个人的做法是直接关掉缓存交给 Redis 这类外部组件统一管理职责更清晰。mybatis-plus: configuration: cache-enabled: false2.4 logImpl 与日志实现的绑定logImpl指定 MyBatis 用哪个日志框架输出 SQL可选值有SLF4J、LOG4J、LOG4J2、JDK_LOGGING、COMMONS_LOGGING、STDOUT_LOGGING、NO_LOGGING。如果你不配MyBatis 会按固定顺序自动查找找到哪个用哪个。这里有个实操经验如果你用的是 Spring Boot默认日志是 Logback对应 SLF4J所以显式配成SLF4J最稳妥。我遇到过项目里同时引入了 Log4j 和 LogbackMyBatis 自动选了 Log4j结果 SQL 日志跑到另一个文件里去了排查起来很费劲。mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl2.5 其他值得关注的配置项defaultStatementTimeout设置 SQL 超时时间秒生产环境强烈建议配上防止慢查询拖垮连接池。defaultExecutorType默认是SIMPLE如果你有大量批量操作可以改成BATCH但要注意BATCH模式下查询结果可能不是你预期的因为批处理会延迟执行。callSettersOnNulls默认false意味着查询结果为 null 的字段不会调用 setter如果你用 Map 接收结果null 字段会直接不出现需要它出现就设为true。这些配置项看着零散但每一个背后都对应着一类真实场景。我的建议是不要一次性全配上用到哪个查哪个配完写注释说明为什么配否则半年后你自己都不记得当初为啥要改。3. GlobalConfig 全局配置MyBatis-Plus 的策略中枢3.1 GlobalConfig 与 Configuration 的分工如果说Configuration是 MyBatis 的大脑那GlobalConfig就是 MyBatis-Plus 加装的“策略中心”。它管的是那些跨所有 Mapper 生效的全局行为主键生成策略、字段自动填充、逻辑删除、SQL 注入器等等。这些能力原生 MyBatis 没有是 MyBatis-Plus 的增值部分。在 Spring Boot 里GlobalConfig通过mybatis-plus.global-config前缀配置。它和Configuration是并列关系各管各的互不干扰。理解这个分工你就不会把db-config的配置写到configuration下面去了——这是新手最常犯的错。3.2 dbConfig 主键策略ASSIGN_ID 还是 AUTOdbConfig.idType是全局主键策略默认是ASSIGN_ID雪花算法。这个默认值在分布式环境下很香因为生成的 ID 全局唯一且趋势递增。但如果你用的是 MySQL 自增主键就必须改成AUTO否则插入时会用雪花 ID 覆盖掉自增导致主键混乱。mybatis-plus: global-config: db-config: id-type: auto这里有个判断标准单库单表用AUTO分库分表或需要提前生成 ID 用ASSIGN_ID。我见过有项目分库分表了还用AUTO结果每个分片都从 1 开始自增ID 直接冲突数据全乱套。3.3 logicDeleteField 逻辑删除的完整配置逻辑删除是 MyBatis-Plus 的招牌功能之一但配置不完整会导致查询把已删除数据也带出来。完整配置需要三部分全局指定逻辑删除字段名、删除值、未删除值。mybatis-plus: global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0配完之后deleteById会自动变成UPDATE ... SET deleted1所有查询自动追加WHERE deleted0。但要注意手写的 XML SQL 不会自动追加这个条件你得自己加。这是很多人踩的坑以为配了全局就万事大吉结果自定义 SQL 把删除数据查出来了。3.4 字段自动填充的配置与实现自动填充createTime、updateTime这类字段能省掉大量重复代码。配置分两步先在GlobalConfig里不用配它靠MetaObjectHandler实现然后在实体类字段上加TableField(fill FieldFill.INSERT)注解最后实现MetaObjectHandler接口。Component public class MyMetaObjectHandler implements MetaObjectHandler { Override public void insertFill(MetaObject metaObject) { this.strictInsertFill(metaObject, createTime, LocalDateTime.class, LocalDateTime.now()); this.strictInsertFill(metaObject, updateTime, LocalDateTime.class, LocalDateTime.now()); } Override public void updateFill(MetaObject metaObject) { this.strictUpdateFill(metaObject, updateTime, LocalDateTime.class, LocalDateTime.now()); } }实操心得strictInsertFill只在字段为 null 时填充如果你手动设了值它不会覆盖。这个特性很有用比如数据迁移时想保留原始创建时间手动设值即可。另外updateFill在updateById时会触发但update(entity, wrapper)这种批量更新不一定触发需要实测确认。3.5 banner 与 SQL 注入器的开关global-config.banner默认true启动时会打印 MyBatis-Plus 的 ASCII 图案。生产环境建议关掉日志干净些。sqlInjector一般不用动除非你要自定义通用方法。DbConfig里的tablePrefix可以给所有表统一加前缀比如t_适合多模块共库时做隔离。4. DbConfig 日志配置让 SQL 无处遁形4.1 日志配置的三种姿势MyBatis-Plus 的 SQL 日志配置本质上还是走 MyBatis 那套logImpl但实际项目里有三种常见做法各有适用场景。第一种是全局log-impl配成StdOutImplSQL 直接打到控制台。开发阶段最省事但生产环境千万别用日志量爆炸且无法分级。mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl第二种是配成Slf4jImpl然后通过 Logback 的 logger 级别控制。这是生产环境的标准做法可以精确到某个 Mapper 包。mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl logging: level: com.example.mapper: debug第三种是用 MyBatis-Plus 的p6spy或PerformanceInterceptor新版已移除改用p6spy做 SQL 性能分析。这个适合排查慢查询能打印执行时间和完整 SQL。4.2 日志级别与包路径的精确控制很多人配了logging.level.com.example.mapper: debug却发现 SQL 没打出来原因通常是 Mapper 接口不在这个包下或者用的是 XML 而 XML 的 namespace 和接口包路径不一致。MyBatis 的日志 logger 名是 Mapper 接口的全限定名所以你的包路径必须精确匹配到接口所在包。我的做法是先配到debug级别确认 SQL 出来了再根据实际需要收窄到具体 Mapper。别一上来就配com.example: debug那样整个项目的日志都会被刷屏。4.3 生产环境日志配置的取舍生产环境我一般这么配log-impl用Slf4jImplMapper 包级别设为info只在排查问题时临时调到debug。同时开启慢 SQL 日志通过p6spy或数据库自身的慢查询日志来捕获。mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl logging: level: com.example.mapper: info这里有个经验SQL 日志里带参数值会暴露敏感信息比如手机号、身份证。生产环境要么不打参数要么做脱敏。MyBatis 默认打的是Preparing: SELECT ... WHERE phone ?加Parameters: 138xxxx(String)参数是明文需要额外处理。4.4 日志配置常见问题速查表现象可能原因解决方向SQL 完全不打印log-impl 未配或配错检查是否配了 Slf4jImpl 或 StdOutImpl只打印 Preparing 不打印 Parameters日志级别不够调到 debug 级别日志重复打印两次引入了多个日志实现排除多余的日志依赖生产环境日志文件暴涨用了 StdOutImpl 或级别过低改 Slf4jImpl info 级别参数显示为 nullcallSettersOnNulls 未开设为 true 或检查参数传递5. 三大配置块的协作关系与实操避坑5.1 配置加载顺序与优先级这三块配置的加载顺序是先ConfigurationMyBatis 原生再GlobalConfigMyBatis-Plus 全局最后DbConfig数据库策略。它们之间没有覆盖关系各管各的。但有一个交叉点主键策略。GlobalConfig.dbConfig.idType是全局默认实体类上的TableId(type IdType.AUTO)是局部覆盖局部优先级高于全局。这个优先级规则很重要。我见过有人在全局配了ASSIGN_ID某个表想用自增就在实体类上加了TableId(type IdType.AUTO)结果发现还是雪花 ID。排查半天才发现是TableId没加value属性或者注解加错了位置。记住局部永远覆盖全局但前提是局部配置生效了。5.2 一个完整的配置示例把三块配置整合到一个application.yml里方便你直接抄作业mybatis-plus: configuration: map-underscore-to-camel-case: true cache-enabled: false log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl default-statement-timeout: 30 global-config: banner: false db-config: id-type: auto logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0 table-prefix: t_ logging: level: com.example.mapper: debug这份配置覆盖了 90% 的日常场景。table-prefix按需加不需要就删掉。default-statement-timeout设 30 秒防止慢查询。5.3 配置不生效的排查思路配置不生效八成是这几个原因配置前缀写错比如把db-config写成dbConfigYAML 里要用中划线、配置类没被扫描到自定义GlobalConfigBean 时忘了Bean、多数据源场景下配置没绑定到正确的 SqlSessionFactory。排查方法很简单启动时打个断点看MybatisPlusProperties里的字段有没有值。或者开debug日志看MybatisPlusAutoConfiguration的日志输出。我一般会在启动类里加一行log.info(MP配置: {}, mybatisPlusProperties)一目了然。5.4 多数据源下的配置隔离多数据源是配置问题的高发区。每个数据源需要独立的SqlSessionFactory也就需要独立的Configuration和GlobalConfig。如果你用ConfigurationProperties绑定记得给每个数据源配不同的前缀。Bean ConfigurationProperties(mybatis-plus.datasource1) public MybatisPlusProperties props1() { return new MybatisPlusProperties(); }实操心得多数据源下GlobalConfig的dbConfig是共享的因为它是全局单例。如果两个数据源的主键策略不同就得在实体类上用TableId局部覆盖没法在全局层面区分。这是 MyBatis-Plus 的一个设计限制用之前要有心理准备。6. 几个真实踩坑案例的复盘6.1 时间类型映射错乱回到开头那个案例。LocalDateTime在 MyBatis 里需要JSR310TypeHandler支持MyBatis 3.4.5 内置了但前提是Configuration里注册了对应的 TypeHandler。MyBatis-Plus 默认会注册但如果你的项目里手动覆盖了TypeHandlerRegistry可能就把默认的冲掉了。解决方法是显式确认configuration.default-enum-type-handler和 JSR310 相关的 handler 是否在。或者更简单在实体类字段上加TableField(typeHandler LocalDateTimeTypeHandler.class)强制指定。6.2 逻辑删除与唯一索引冲突逻辑删除有个经典问题表上有个唯一索引uk_username删除用户 A 后再注册同名用户会报唯一键冲突因为已删除的记录还在表里。解决方案有两种一是把唯一索引改成(username, deleted)联合索引二是删除时把 username 改成username_deleted_时间戳。我倾向第二种改动小且不影响查询。6.3 自动填充在批量插入时失效MetaObjectHandler的insertFill在saveBatch时是生效的但在自定义的insertBatchSomeColumn里可能不生效因为后者走的是自定义 SQL 注入器。如果遇到批量插入时间字段为空检查一下是不是用了自定义注入器是的话需要在 SQL 里手动加填充逻辑。7. 配置管理的个人经验谈配了这么多年 MyBatis-Plus我最大的体会是配置项不是越多越好而是越明确越好。每个配置项都应该有存在的理由要么解决了一个具体问题要么规避了一类风险。那些“别人配了我也配”的项往往就是日后出问题的隐患。另外配置一定要和代码一起进版本控制并且写清楚注释。我见过太多项目application.yml里一堆配置没有任何说明换个人接手根本不敢动。我的做法是在每个非默认配置上方加一行注释说明为什么这么配、不这么配会怎样。这个习惯帮我省了无数次排查时间。最后一个建议新项目初始化时先把这三块配置按最小可用原则配好跑通一个增删改查再逐步加功能。别一上来就把所有高级特性都打开出了问题你都不知道是哪个配置引起的。配置这东西跟做菜一样盐要一点点加一次加到位容易齁着。