当前位置: 首页 > news >正文

告别繁琐SQL!Spring Boot 3.2 + MybatisPlus 3.5.x 配置与常用注解避坑指南

Spring Boot 3.2 + MyBatis-Plus 3.5.x 实战避坑手册:从配置陷阱到注解玄学

当你在深夜调试MyBatis-Plus时,是否经历过这样的绝望瞬间——明明按照教程一步步操作,启动时却报出Invalid bound statement?或是发现@TableField注解像被施了隐身咒般毫无反应?本文将带你直击这些"灵异现象"背后的真相,用外科手术式精准拆解配置与注解的每个技术细节。

1. 那些年我们踩过的配置坑

1.1 配置文件:YAML的温柔陷阱

在Spring Boot 3.2中,application.yml的缩进就像Python的缩进一样致命。来看个典型错误示例:

mybatis-plus: mapper-locations: classpath*:/mapper/**/*.xml type-aliases-package: com.example.entity # 这里缩进错误!

这个配置会导致TypeAliases扫描完全失效。正确的姿势应该是:

mybatis-plus: mapper-locations: classpath*:/mapper/**/*.xml type-aliases-package: com.example.entity # 保持同级缩进

关键配置项对照表

配置项默认值典型错误正确示例
mapper-locationsclasspath:/mapper/*.xmlclasspath*:/mapper/**/*.xml
type-handlers-packagecom.example.handlercom.example.typehandler
configuration.map-underscore-to-camel-casefalsetrue(3.4.x旧版)true(3.5.x需要额外配置)

提示:在MyBatis-Plus 3.5.x中,开启驼峰映射需要额外添加:

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

1.2 多数据源下的死亡缠绕

当项目需要连接多个数据库时,常见的连环坑包括:

  1. Primary注解遗漏
@Configuration public class DataSourceConfig { @Bean @Primary // 这个注解绝对不能少! public DataSource firstDataSource() {...} @Bean public DataSource secondDataSource() {...} }
  1. Mapper扫描范围重叠
@MapperScan(basePackages = "com.dao.mysql", sqlSessionTemplateRef = "mysqlTemplate") public class MysqlConfig {} @MapperScan(basePackages = "com.dao.postgres", sqlSessionTemplateRef = "postgresTemplate") public class PostgresConfig {}

如果两个包路径存在包含关系,就会引发BeanDefinitionOverrideException。建议采用严格的包名隔离:

com.dao.mysql com.dao.postgresql

2. 注解背后的黑暗魔法

2.1 @TableName:你以为的简单其实不简单

这个看似简单的注解藏着三个致命陷阱:

场景一:表名前缀统一配置

@TableName("user") // 当全局配置了表前缀时,实际表名会是"t_user" public class User {}

解决方案:

@TableName(value = "user", keepGlobalPrefix = false) // 显式禁用全局前缀

场景二:动态表名切换

public class User { @TableField(exist = false) private String tableSuffix; public String getDynamicTableName() { return "user_" + tableSuffix; } }

需要在配置类中注册动态表名处理器:

public class MybatisPlusConfig { @Bean public DynamicTableNameParser dynamicTableNameParser() { return (sql, metaObject) -> { Object tableSuffix = metaObject.getValue("tableSuffix"); return tableSuffix != null ? "user_" + tableSuffix : "user"; }; } }

2.2 @TableId:主键类型的修罗场

主键类型配置错误会导致各种诡异问题:

// 雪花ID配置(MySQL的BIGINT) @TableId(type = IdType.ASSIGN_ID) private Long id; // 自增主键(需要数据库设置AUTO_INCREMENT) @TableId(type = IdType.AUTO) private Integer id; // 全局ID生成器(需实现IdentifierGenerator接口) @TableId(type = IdType.ASSIGN_UUID) private String id;

常见踩坑点

  • 数据库字段是BIGINT但实体类用Integer
  • 忘记在application.yml配置id-type: ASSIGN_ID
  • 混用AUTOASSIGN_ID策略

2.3 @TableField:映射的七十二变

这个注解的复杂程度远超想象:

Case 1:字段填充策略

@TableField(fill = FieldFill.INSERT) private LocalDateTime createTime; @TableField(fill = FieldFill.INSERT_UPDATE) private LocalDateTime updateTime;

需要实现元对象处理器:

@Component public class MyMetaObjectHandler implements MetaObjectHandler { @Override public void insertFill(MetaObject metaObject) { this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now()); } @Override public void updateFill(MetaObject metaObject) { this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now()); } }

Case 2:非表字段的三种处理方式

// 方式1:静态忽略 @TableField(exist = false) private String tempValue; // 方式2:动态忽略(通过Wrapper) queryWrapper.select(Student.class, info -> !info.getColumn().equals("temp_value")); // 方式3:条件判断 @TableField(condition = SqlCondition.LIKE) private String name;

3. 高阶玩家的性能调优

3.1 SQL注入器:自定义魔法

实现一个逻辑删除增强器:

public class MyLogicSqlInjector extends DefaultSqlInjector { @Override public List<AbstractMethod> getMethodList(Class<?> mapperClass) { List<AbstractMethod> methodList = super.getMethodList(mapperClass); methodList.add(new DeleteAll()); // 添加自定义方法 return methodList; } } public class DeleteAll extends AbstractMethod { @Override public MappedStatement injectMappedStatement(...) { String sql = "delete from " + tableInfo.getTableName(); SqlSource sqlSource = languageDriver.createSqlSource(...); return this.addDeleteMappedStatement(mapperClass, "deleteAll", sqlSource); } }

注册到Spring容器:

@Bean public MyLogicSqlInjector myLogicSqlInjector() { return new MyLogicSqlInjector(); }

3.2 分页插件性能优化

默认分页在百万级数据时会出现性能问题:

@Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); // 优化版分页插件 PaginationInnerInterceptor paginationInterceptor = new PaginationInnerInterceptor(DbType.MYSQL); paginationInterceptor.setOptimizeJoin(true); // 优化联表查询 paginationInterceptor.setMaxLimit(1000L); // 单页最大1000条 interceptor.addInnerInterceptor(paginationInterceptor); return interceptor; }

性能对比数据

数据量传统分页(ms)优化分页(ms)
10万1200450
50万58002100
100万超时4200

4. 调试技巧:从灵异到科学

4.1 日志配置的黄金组合

application.yml中添加:

logging: level: com.baomidou.mybatisplus: DEBUG org.springframework.jdbc.datasource: TRACE

配合MyBatis-Plus原生日志:

mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

4.2 元数据检查工具

在测试类中添加诊断方法:

@Test void showMetaData() { TableInfoHelper.getTableInfos().forEach(tableInfo -> { System.out.println("=== Table: " + tableInfo.getTableName() + " ==="); tableInfo.getFieldList().forEach(field -> { System.out.println(field.getProperty() + " -> " + field.getColumn()); }); }); }

输出示例:

=== Table: user === id -> user_id name -> user_name createTime -> create_time

4.3 异常速查手册

异常信息可能原因解决方案
Invalid bound statementmapper.xml路径错误检查mapper-locations配置
Unknown column 'xxx' in 'field list'字段映射失败检查@TableField或开启map-underscore-to-camel-case
Could not set parameters类型不匹配检查实体类与数据库字段类型
Table 'database.table' doesn't exist表名前缀问题检查@TableName的keepGlobalPrefix
http://www.jsqmd.com/news/670852/

相关文章:

  • Harness 架构 与 LangChain、LangGraph 三者联动 的底层逻辑 。<Harness 学习圣经> 之二
  • 深入HTTP/2帧层:手把手用Wireshark抓包分析GOAWAY帧与gRPC连接管理
  • 保姆级教程:手把手教你给PnetLab添加自定义网络设备镜像(附常见错误排查)
  • Linux基础命令(文件目录类)
  • 从输入URL到网页打开:彻底搞懂 IP、ARP、ICMP 是如何分工协作的
  • 深聊祥辉双面胶保护膜,好用吗?价格和靠谱程度大揭秘 - 工业设备
  • 第38篇:AI在金融领域的应用实战——智能投顾、风控与量化交易初探(项目实战)
  • Python 文件操作实战:从入门到精通,一文学会打开 / 读取 / 修改文件
  • Bebas Neue:3大优势解决设计师字体选择的终极难题
  • 盘点口碑好的铜铝电缆回收源头厂家,选哪家比较好 - 工业推荐榜
  • 不到百元捡漏乐视Astra Pro深度摄像头,手把手教你用Python+OpenCV玩转深度图与彩色图
  • 保姆级教程:用BiSeNetv2在Cityscapes数据集上跑通语义分割(附避坑指南)
  • 谷歌Gemini个人智能:个性化AI体验新突破,告别反复提供上下文信息的麻烦!
  • 告别全局字体缩放:手把手教你锁定App内部DPI,无视系统显示大小设置
  • STM32F103+ESP8266做智能开关?手把手教你从硬件接线到APP远程控制(附完整工程)
  • 从数据库索引到社交网络:用5个真实案例吃透离散数学的‘关系’与‘图’
  • RAG 检索增强生成:详细原理 + Python 完整实战
  • 如何用 vLCM 统一管理 ESXi 更新?镜像 + 驱动 + 组件基线一站式管理教程
  • 离线报文回放步骤 CANalyzer 9.0 /CANoe
  • PyTorch 2.8镜像快速上手:Python零基础入门深度学习的第一课
  • 5分钟搭建Testsigma:零代码自动化测试的完整解决方案
  • 如何永久保存微信聊天记录?本地免费工具WeChatMsg完整指南
  • 小心!这些看似普通的汉字特殊符号,可能会让你的代码和文档出大问题
  • Python Web服务器网关接口:WSGI、ASGI、RSGI、uWSGI、uwsgi、Gunicorn、Uvicorn
  • 2026年适合自学的自动打分雅思机考网站推荐 - 品牌2026
  • 如何免费将视频硬字幕转为SRT文件?本地OCR工具终极指南
  • CLIP-GmP-ViT-L-14图文匹配工具效果实录:模糊图片仍保持高区分度匹配
  • 告别模式困惑:深入解读Mellanox VPI网卡的LINK_TYPE_P1参数与网络协议栈选择
  • Kook Zimage 真实幻想 Turbo入门教程:从零开始的Linux环境部署
  • 为什么你的万爱通礼品卡被闲置?四个实用回收技巧让它不再浪费 - 团团收购物卡回收