一、报错信息
在 Spring Boot + MyBatis 项目启动时,你可能会遇到如下异常:
org.springframework.context.annotation.ConflictingBeanDefinitionException:
Annotation-specified bean name 'sysConfigMapper' for bean class [com.example.item.mapper.SysConfigMapper]
conflicts with existing, non-compatible bean definition of same name and class [com.example.contract.mapper.SysConfigMapper]
at org.springframework.context.annotation.ClassPathBeanDefinitionScanner.checkCandidate(ClassPathBeanDefinitionScanner.java:361)
at org.mybatis.spring.mapper.ClassPathMapperScanner.checkCandidate(ClassPathMapperScanner.java:364)
at org.springframework.context.annotation.ClassPathBeanDefinitionScanner.doScan(ClassPathBeanDefinitionScanner.java:288)
at org.mybatis.spring.mapper.ClassPathMapperScanner.doScan(ClassPathMapperScanner.java:255)
...
关键信息解读:
- 冲突的 Bean 名称 :
sysConfigMapper - 冲突的两个类 :
com.example.item.mapper.SysConfigMappercom.example.contract.mapper.SysConfigMapper
- 根本原因 :两个不同包路径下的接口类名完全相同,导致 Spring 在注册 Bean 时生成了相同的默认名称,从而引发冲突。
二、问题根源
2.1 Spring 默认 Bean 命名规则
Spring 在未显式指定 Bean 名称时,会使用以下规则生成名称:
- 对于类
XxxMapper,默认 Bean 名为xxxMapper(首字母小写)。 - 此逻辑由
AnnotationBeanNameGenerator实现。
2.2 MyBatis Mapper 扫描机制
当使用 @MapperScan("com.example.**.mapper") 时,MyBatis-Spring 会:
- 扫描指定包下所有接口;
- 为每个接口创建一个代理 Bean;
- 使用默认命名策略生成 Bean 名称;
- 尝试注册到 Spring 容器。
2.3 冲突触发条件
同时满足以下两点即会报错:
- 两个 Mapper 接口类名完全相同 (如都叫
SysConfigMapper); - 位于不同的包路径下 (如
moduleA.mapper和moduleB.mapper)。
此时,Spring 认为这是两个"不兼容"的 Bean 定义(因为它们是不同的 Class 对象),拒绝注册,抛出 ConflictingBeanDefinitionException。
三、解决方案
⚠️ 重要更正 :
网上部分资料提到使用
@Mapper("name")指定名称,这是错误的 。
@Mapper是 MyBatis 的注解,不支持 value 参数 ,无法用于指定 Spring Bean 名称。正确做法是使用 Spring 的
@Repository注解。
✅ 方案一:重命名 Mapper 接口(强烈推荐)
这是最根本、最清晰的解决方案。
操作步骤:
-
将其中一个接口重命名为语义化名称:
java// moduleA public interface ItemSysConfigMapper { // ... }java// moduleB public interface ContractSysConfigMapper { // ... } -
在 Service 中通过类型注入:
java@Service public class SomeService { @Autowired private ItemSysConfigMapper itemSysConfigMapper; @Autowired private ContractSysConfigMapper contractSysConfigMapper; }
优势:
- 彻底消除冲突;
- 提高代码可读性与可维护性;
- 符合 Clean Code 原则。
✅ 方案二:使用 @Repository("uniqueName") 显式指定 Bean 名称
如果因历史原因无法修改接口名,可通过 Spring 的 @Repository 注解指定唯一名称。
正确写法:
java
// moduleA.mapper.SysConfigMapper
@Repository("itemSysConfigMapper")
public interface SysConfigMapper {
// ...
}
java
// moduleB.mapper.SysConfigMapper
@Repository("contractSysConfigMapper")
public interface SysConfigMapper {
// ...
}
注入方式(必须匹配):
java
@Service
public class SomeService {
// 方式 1:按名称注入(不推荐,字符串耦合)
@Resource(name = "itemSysConfigMapper")
private SysConfigMapper itemMapper;
// 方式 2:按类型注入(推荐,但需明确类型路径)
@Autowired
private com.example.moduleA.mapper.SysConfigMapper itemSysConfigMapper;
}
❗ 注意:
@Mapper注解本身不能指定名称,它仅用于标记接口为 MyBatis Mapper;@Repository是 Spring 的注解,用于声明一个 Bean,并支持指定名称;- 两者可共存,但只有
@Repository能控制 Bean 名。
✅ 方案三:自定义 BeanNameGenerator(适用于大型系统)
若项目中存在大量同名 Mapper 且无法逐一修改,可全局自定义命名策略。
实现:
java
public class UniqueMapperNameGenerator implements BeanNameGenerator {
@Override
public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
// 使用全类名作为 Bean 名,确保绝对唯一
return definition.getBeanClassName();
}
}
配置:
java
@SpringBootApplication
@MapperScan(
basePackages = "com.example",
nameGenerator = UniqueMapperNameGenerator.class
)
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
注意事项:
- 所有 Mapper 的 Bean 名将变为完整类名(如
com.example.moduleA.mapper.SysConfigMapper); - 若代码中使用了
@Resource(name = "xxx"),需同步更新; - 建议配合全面回归测试。
✅ 方案四:分包扫描(多数据源场景)
适用于模块职责清晰、操作不同数据库的架构。
java
@Configuration
public class MyBatisConfig {
@MapperScan(
basePackages = "com.example.moduleA.mapper",
sqlSessionFactoryRef = "moduleASqlSessionFactory"
)
public static class ModuleAMapperScan {}
@MapperScan(
basePackages = "com.example.moduleB.mapper",
sqlSessionFactoryRef = "moduleBSqlSessionFactory"
)
public static class ModuleBMapperScan {}
}
该方案通过物理隔离避免冲突,但仅适用于多数据源或严格分层架构。
四、预防措施
为避免此类问题,建议团队遵循以下规范:
| 规范 | 说明 |
|---|---|
| 命名唯一性 | Mapper 接口应包含业务/模块前缀,如 UserOrderMapper |
| 包结构清晰 | 按功能域划分包,避免跨模块复用同名接口 |
| 优先类型注入 | 使用 @Autowired + 具体类型,而非 @Resource(name=...) |
| 禁止依赖默认名 | 不要假设 XxxMapper 的 Bean 名就是 xxxMapper |
| 代码审查 | 在 CR 中检查 Mapper 命名是否可能冲突 |