解决 org.springframework.context.annotation.ConflictingBeanDefinitionException 报错

一、报错信息

在 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.SysConfigMapper
    • com.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 会:

  1. 扫描指定包下所有接口;
  2. 为每个接口创建一个代理 Bean;
  3. 使用默认命名策略生成 Bean 名称;
  4. 尝试注册到 Spring 容器。

2.3 冲突触发条件

同时满足以下两点即会报错:

  1. 两个 Mapper 接口类名完全相同 (如都叫 SysConfigMapper);
  2. 位于不同的包路径下 (如 moduleA.mappermoduleB.mapper)。

此时,Spring 认为这是两个"不兼容"的 Bean 定义(因为它们是不同的 Class 对象),拒绝注册,抛出 ConflictingBeanDefinitionException


三、解决方案

⚠️ 重要更正

网上部分资料提到使用 @Mapper("name") 指定名称,这是错误的
@Mapper 是 MyBatis 的注解,不支持 value 参数 ,无法用于指定 Spring Bean 名称。

正确做法是使用 Spring 的 @Repository 注解。


✅ 方案一:重命名 Mapper 接口(强烈推荐)

这是最根本、最清晰的解决方案。

操作步骤:
  1. 将其中一个接口重命名为语义化名称:

    java 复制代码
    // moduleA
    public interface ItemSysConfigMapper {
        // ...
    }
    java 复制代码
    // moduleB
    public interface ContractSysConfigMapper {
        // ...
    }
  2. 在 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 命名是否可能冲突
相关推荐
大飞哥~BigFei2 小时前
整数ID与短字符串互转思路及开源实现分享
java·开源
benjiangliu2 小时前
LINUX系统-09-程序地址空间
android·java·linux
历程里程碑2 小时前
子串-----和为 K 的子数组
java·数据结构·c++·python·算法·leetcode·tornado
独自破碎E2 小时前
字符串相乘
android·java·jvm
东东5162 小时前
OA自动化居家办公管理系统 ssm+vue
java·前端·vue.js·后端·毕业设计·毕设
没有bug.的程序员2 小时前
Spring Cloud Alibaba:Nacos 配置中心与服务发现的工业级深度实战
java·spring boot·nacos·服务发现·springcloud·配置中心·alibaba
一只大袋鼠2 小时前
分布式 ID 生成:雪花算法原理、实现与 MyBatis-Plus 实战
分布式·算法·mybatis
rainbow68892 小时前
Java并发三要素:原子性、可见性、有序性
java
小罗和阿泽2 小时前
复习 Java(2)
java·开发语言