一个Mybatisplus组件扫描不当引起的bug:弄巧成拙,认真的锅,自我怀疑

article/2025/7/22 18:24:46

在我们系统基建层的业务组件包 sby-biz-component 中,最初,我写了两个业务组件,一个是 通道错误码组件,一个是 审核流水组件。
这两个业务组件都要依赖Mybatisplus来操作数据。

com.sby.bizcomponent├── auditflow│ └── AuditMapper.java│ └── AuditService.java│ └── AuditAutoConfiguration.java│ └── MybatisPlusMapperScanConfig.java│ └── package-info.java└── payerrorcode└── PayErrorCodeMapper.java└── PayErrorCodeService.java└── PayErrorCodeAutoConfiguration.java└── MybatisPlusMapperScanConfig.java└── package-info.java

其中的 AuditAutoConfiguration.javaPayErrorCodeAutoConfiguration.java实现组件的自动装配。以 payerrorcode/PayErrorCodeAutoConfiguration.java 为例,代码如下。

@Configuration
@ComponentScan(basePackageClasses = {PayErrorCodeAutoConfiguration.class})
@ConditionalOnProperty(name = "bizcomponent.payerrorcode.enable",havingValue = "true")
public class PayErrorCodeAutoConfiguration {
}

这里要说的是两个 MybatisPlusMapperScanConfig.java,两者的职责都是用来指定 Mybatisplus的 @MapperScan 注解,来定义Mapper接口的扫描路径。

// payerrorcode/MybatisPlusMapperScanConfig.java
@Configuration
@MapperScan(value = {"com.serviceshare.bizcomponent.payerrorcode"})
public class MybatisPlusMapperScanConfig {
}// auditflow/MybatisPlusMapperScanConfig.java
@Configuration
@MapperScan(value = {"com.serviceshare.bizcomponent.auditflow"})
public class MybatisPlusMapperScanConfig {
}

显然,这两个类重复度极高。对于此类代码,我的一贯风格是,盘它!我一向比较认真地致力于构建易于维护的应用系统。

我的方案是,我在 sby-biz-component 里新增了一个名为“config”的package。

com.sby.bizcomponent├── auditflow│ └── AuditMapper.java│ └── AuditService.java│ └── AuditAutoConfiguration.java│ └── package-info.java├── config│ └── MybatisPlusMapperScanConfig.java└── payerrorcode└── PayErrorCodeMapper.java└── PayErrorCodeService.java└── PayErrorCodeAutoConfiguration.java└── package-info.java

改动后的 config/MybatisPlusMapperScanConfig.java,代码如下:

@Configuration
@MapperScan(value = {"com.serviceshare.bizcomponent.*.**"})
public class MybatisPlusMapperScanConfig {
}

同时,修改两个 AutoConfiguration类,增加对MybatisPlusMapperScanConfig的扫描。例如 payerrorcode/PayErrorCodeAutoConfiguration.java:

@Configuration
@ComponentScan(basePackageClasses = {PayErrorCodeAutoConfiguration.class,MybatisPlusMapperScanConfig.class})
@ConditionalOnProperty(name = "bizcomponent.payerrorcode.enable",havingValue = "true")
public class PayErrorCodeAutoConfiguration {
}

调整后的代码投产后,依赖这个 sby-biz-component 组件的应用服务,没有问题。

不过,好景持续了一年之久。

最近,我在 sby-biz-component 中,新开发了记账的业务组件。

然后,问题出现了————我们的一个依赖了 sby-biz-component 的中台 zhongtai-base 服务,本地环境启动报错:org.springframework.boot.SpringApplication:837 - Application run failed:org.springframework.context.annotation.ConflictingBeanDefinitionException: Annotation-specified bean name 'accountMapper' for bean class [com.serviceshare.bizcomponent.accounting.service.AccountMapper] conflicts with existing, non-compatible bean definition of same name and class [org.mybatis.spring.mapper.MapperFactoryBean]

这个错误是说,sby-biz-component 里 AccountMapper 与当前 zhongtai-base 服务里的 AccountMapper 存在bean冲突。

错误中提到的 sby-biz-component 里的 AccountMapper,在我新开发的记账组件package中。

这令我和一个开发伙伴摸不着头脑。

经过进一步分析和思考后,发现症结所在。

这牵涉到我去年后来在sby-biz-component 中开发的 datamig 数据迁移组件。
与其他组件所不同的是,这个 datamig 组件是自动生效的,不需要@ConditionalOnProperty配置。见下方 datamig.TimeBasedDataMigAutoConfiguration 代码。

// datamig/TimeBasedDataMigAutoConfiguration.java@Configuration
@ComponentScan(basePackageClasses = {TimeBasedDataMigAutoConfiguration.class, MybatisPlusMapperScanConfig.class})
//@ConditionalOnProperty(name = "bizcomponent.datamig.enable",havingValue = "true")//**默认开启数据迁移组件
public class TimeBasedDataMigAutoConfiguration {
}

问题就在于 datamig 的这个自动配置class,它扫描了 MybatisPlusMapperScanConfig, 这就导致 sby-biz-component 中所有的 Mapper接口类都作为spring容器的bean生效了。那么,这自然就包括新开发的记账组件package中的 AccountMapper。而 zhongtai-base 项目中自身有 AccountMapper, 就会出现 bean冲突异常,导致 zhongtai-base 服务无法启动。

这引起了我的自我反思。本意是为了降低重复,提升代码可维护性。结果却“弄巧成拙”了。生活中,对于我来说,也存在类似的情况。例如,刷完碗,为了控水,我会把一个碗倒扣在另外两个碗上,然后,自己在忙活中,不小心把碗碰到了地上,结果就碎碎平安了~

回归正题。我很快想到了fix这个问题的方案————

删掉 config.MybatisPlusMapperScanConfig.java,并由每个组件自己的自动配置类里,来指定Mybatisplus的 @MapperScan。

// payerrorcode/PayErrorCodeAutoConfiguration.java
@Configuration
@ComponentScan(basePackageClasses = {PayErrorCodeAutoConfiguration.class})
@MapperScan(basePackageClasses = {PayErrorCodeAutoConfiguration.class})
@ConditionalOnProperty(name = "bizcomponent.payerrorcode.enable",havingValue = "true")
public class PayErrorCodeAutoConfiguration {
}// auditflow/AuditAutoConfiguration.java
@Configuration
@ComponentScan(basePackageClasses ={ AuditAutoConfiguration.class})
@MapperScan(basePackageClasses = {AuditAutoConfiguration.class})
@ConditionalOnProperty(name = "bizcomponent.audit.enable",havingValue = "true")
public class AuditAutoConfiguration {
}// datamig/TimeBasedDataMigAutoConfiguration.java
@Configuration
@ComponentScan(basePackageClasses = {TimeBasedDataMigAutoConfiguration.class})
@MapperScan(basePackageClasses = {TimeBasedDataMigAutoConfiguration.class})
//@ConditionalOnProperty(name = "bizcomponent.datamig.enable",havingValue = "true")//默认开启数据迁移组件
public class TimeBasedDataMigAutoConfiguration {
}// accounting/AccountingAutoConfiguration.java
@Configuration
@ComponentScan(basePackageClasses = {AccountingAutoConfiguration.class, DDLCreator.class})
@MapperScan(basePackageClasses = {AccountingAutoConfiguration.class})
@ConditionalOnProperty(name = "bizcomponent.accounting.enable",havingValue = "true")
public class AccountingAutoConfiguration {
}

显然,这个方案既没有增加重复代码。如果我当初在解决重复代码时,使用这个方案,就不会遇到本文提到的这个问题了。

话说回来,在系统开发中,我常常为了追求一些自认为美好的东西,会认真地进行一些努力和实践。例如本文说的消除代码重复,再例如完善代码的javadoc,再例如优化异步调用方式,再例如更正数据类型、重命名变量/参数名,再例如使用enum提高可读性。

有时呢,后来证明,我的认真,反而让我犯了错。一些努力和实践带来了一些负面影响,然后我要为我的“认真”付出代价。当然,我始终认为这些是应该做的,应该致力于做。当然,有时候,也难免自我怀疑、自我否定。人大概就是在不断地自我摸索、自我怀疑、自我否定中成长吧!


http://www.hkcw.cn/article/OAsTaFZWsS.shtml

相关文章

t015-预报名管理系统设计与实现 【含源码!!!】

项目演示地址 摘 要 传统办法管理信息首先需要花费的时间比较多,其次数据出错率比较高,而且对错误的数据进行更改也比较困难,最后,检索数据费事费力。因此,在计算机上安装预报名管理系统软件来发挥其高效地信息处理的…

数学建模之最短路径问题

1 问题的提出 这个是我们的所要写的题目,我们要用LINGO编程进行编写这个题目,那么就是需要进行思考这个怎么进行构建这个问题的模型 首先起点,中间点,终点我们要对这个进行设计 2 三个点的设计 起点的设计 起点就是我们进去&am…

刀具问题讨论

1 刀具的问题概述 问题描述 一道工序用自动化车床连续加工某种零件,由于刀具损坏等原因该工序会出现故障,其中刀具损坏故障占95%, 其它故障仅占 5%。工序出现故障是完全随机的, 假定在生产任一零件时出现故障的机会均相同。工作人员通过检查零件来确定…

stm32 / arduino TPL0401A使用教程

这是在给英国的一个学生讲课时用到的一个芯片,做一个dcdc的反馈电路,刚开始用的不是这个,后来发现国内这个芯片用的挺成熟,就选择了这个。 芯片说明 首先我买的是TPL0401A,我发现淘宝上卖的都是A,其实想用C&#xff0…

进程调度策略和进程优先级

Linux 的进程调度策略和进程优先级是操作系统为保证系统响应性、公平性和高性能所设计的关键机制。 进程调度策略 Linux 支持 两大类调度策略: 普通调度策略(CFS: Completely Fair Scheduler), 适用于大部分用户态进程。实时调…

「Java教案」Java程序的构成

课程目标 1.知识目标 能够按照Java标识符的命名规则,规范变量的命名。能够区分Java中的关键字与保留字。能够对注释进行分类,根据注释的用途合理的选择注释方式。 2.能力目标 能编写符合规范的标识符。能识别Java中的关键字和…

随记 配置服务器的ssl整个过程

第一步 先了解到这个公钥私钥服务器自己可以生成,但是没什么用,浏览器不会信任的,其他人访问不了。所以要一些中间机构颁布的证书才有用。 一般的服务器直接 安装 Certbot 和插件 //CentOS Nginx 用户: sudo yum install epe…

Spring Cloud 知识

Spring Cloud 知识 一. 服务注册与发现1. Eureka1. Eureka 的概念2. Eureka 的特点3. Eureka 的应用场景4. Eureka 的实现原理 2. Nacos1. Nacos 的概念2. Nacos 的特点3. Nacos 的应用场景4. Nacos 的实现原理1. 服务注册与发现:2. 配置管理:3. 一致性算…

半导体晶圆制造洁净厂房的微振控制方案-江苏泊苏系统集成有限公司

半导体晶圆制造洁净厂房的微振控制方案-江苏泊苏系统集成有限公司 微振控制在现行国家标准《电子工业洁净厂房设计规范》GB50472中有关微振控制的规定主要有:洁净厂房的微振控制设施的设计分阶段进行,应包括设计、施工和投产等各阶段的微振测试、厂房建…

《操作系统真相还原》——大战MBR

在开机的一瞬间,也就是接电的一瞬间,CPU 的 cs:ip 寄存器被强制初始化为 0xF000:0xFFF0。由于开机的时候处于实模式,再重复一遍加深印象,在实模式下的段基址要乘以16,也就是左移4位,…

【计算机网络】fork()+exec()创建新进程(僵尸进程及孤儿进程)

文章目录 一、基本概念1. fork() 系统调用2. exec() 系列函数 二、典型使用场景1. 创建子进程执行新程序2. 父子进程执行不同代码 三、核心区别与注意事项四、组合使用技巧1. 重定向子进程的输入/输出2. 创建多级子进程 五、常见问题与解决方案僵尸进程(Zombie Proc…

Selenium操作指南(全)

🍅 点击文末小卡片,免费获取软件测试全套资料,资料在手,涨薪更快 大家好,今天带大家一起系统的学习下模拟浏览器运行库Selenium,它是一个用于Web自动化测试及爬虫应用的重要工具。 Selenium测试直接运行在…

Linux研学-入门命令

一 目录介绍 1 介绍 Linux与Windows在目录结构组织上差异显著:Linux采用树型目录结构,以单一根目录/为起点,所有文件和子目录由此向下延伸形成层级体系,功能明确的目录各司其职,使文件系统层次清晰、逻辑连贯&#xf…

GSR 手环能耗数据实测:STM32 与 SD NAND 的功耗优化成果

文章目录 卓越性能强化安全高效能效图形处理优势丰富集成特性模拟模块实时监控保障数据完整性提升安全性与可靠性测量原理采样率相关 在智能皮电手环及数据存储技术不断迭代的当下,主控 MCU STM32H750 与存储 SD NAND MKDV4GIL-AST 的强强联合,正引领行业…

PCIe-PCI、PCIe中断机制概述

PCI、PCIe中断概述 PCIe 中断机制在继承 PCI 传统中断(INTx)的基础上,引入了更高效的 MSI/MSI-X 方案,以提升设备性能并减少 CPU 轮询开销。以下是核心要点及技术演进: ⚙️ ​​一、PCIe 中断类型与演进​​ ​​IN…

知识图谱增强的大型语言模型编辑

https://arxiv.org/pdf/2402.13593 摘要 大型语言模型(LLM)是推进自然语言处理(NLP)任务的关键,但其效率受到不准确和过时知识的阻碍。模型编辑是解决这些挑战的一个有前途的解决方案。然而,现有的编辑方法…

建立连接后 TCP 请求卡住

大家读完觉得有意义记得关注和点赞!!! 这篇文章描述了一个内核和BPF网络问题 以及故障排除步骤,这是一个值得深入研究的有趣案例 Linux 内核网络复杂性。 目录 1 故障报告 1.1 现象:概率健康检查失败 1.2 范围&am…

C++核心编程_赋值运算符重载

4.5.4 赋值运算符重载 c编译器至少给一个类添加4个函数 默认构造函数(无参,函数体为空) 默认析构函数(无参,函数体为空) 默认拷贝构造函数,对属性进行值拷贝 赋值运算符 operator, 对属性进行值拷贝 如果类中有属性指向堆区,做…

深度学习笔记25-RNN心脏病预测(Pytorch)

🍨 本文为🔗365天深度学习训练营中的学习记录博客🍖 原作者:K同学啊 一、前期准备 1.数据处理 import torch.nn.functional as F import numpy as np import pandas as pd import torch from torch import nn dfpd.read_csv(r&…

基于 HT for Web 轻量化 3D 数字孪生数据中心解决方案

一、技术架构:HT for Web 的核心能力 图扑软件自主研发的 HT for Web 是基于 HTML5 的 2D/3D 可视化引擎,核心技术特性包括: 跨平台渲染:采用 WebGL 技术,支持 PC、移动端浏览器直接访问,兼容主流操作系统…