springboot项目现在有一个DashBoardTagMapper文件在两个不同的模块A和模块B中,如何通过@Bean注解的方式将两个DashBoardTagMapper文件区分开,启动时报错:2025-08-11 10:54:36.772 [main] ERROR ${env:APP_ENV} [] [o.s.b.SpringApplication:reportFailure-818] --- Application run failed org.springframework.context.annotation.ConflictingBeanDefinitionException: Annotation-specified bean name 'dashBoardTagMapper' for bean class [com.huawei.libing.cloudsec.model.dao.mapper.DashBoardTagMapper] conflicts with existing, non-compatible bean definition of same name and class [com.huawei.libing.cloudsec.whitebox.dao.mapper.DashBoardTagMapper] ,如何通过@Bean注解的方式将两个DashBoardTagMapper区分开
时间: 2025-08-11 09:08:29 AIGC 浏览: 19
<think>由于两个模块中存在同名的Mapper接口(DashBoardTagMapper),在Spring Boot中扫描时会产生冲突。
解决方案:为两个Mapper指定不同的Bean名称,并在注入时使用@Qualifier注解区分。
步骤:
1. 在每个Mapper接口上使用@Mapper注解(如果使用MyBatis)时,可以指定bean的名称。
2. 或者,在配置类中通过@Bean注解手动注册这两个Mapper,并指定不同的Bean名称。
但是,由于是MyBatis的Mapper接口,通常我们使用@MapperScan来扫描,这样每个Mapper都会生成一个Bean,默认的Bean名称是类名首字母小写(即dashBoardTagMapper)。当两个模块中存在相同简单类名的Mapper时,就会冲突。
因此,我们可以通过以下两种方式之一解决:
方式一:在@MapperScan中指定不同的Bean名称生成规则(例如,使用全限定类名作为Bean名称)
方式二:在配置类中分别定义两个Bean,并指定不同的Bean名称。
这里我们采用方式二,因为两个Mapper在不同的包下,我们可以通过配置类分别定义它们,并指定不同的Bean名称。
具体步骤:
在模块A的配置类中:
```java
@Configuration
public class ModuleAConfig {
@Bean
public DashBoardTagMapper moduleADashBoardTagMapper(SqlSessionTemplate sqlSessionTemplate) {
return sqlSessionTemplate.getMapper(com.huawei.libing.cloudsec.model.dao.mapper.DashBoardTagMapper.class);
}
}
```
在模块B的配置类中:
```java
@Configuration
public class ModuleBConfig {
@Bean
public DashBoardTagMapper moduleBDashBoardTagMapper(SqlSessionTemplate sqlSessionTemplate) {
return sqlSessionTemplate.getMapper(com.huawei.libing.cloudsec.whitebox.dao.mapper.DashBoardTagMapper.class);
}
}
```
这样,两个Bean的名称分别为:moduleADashBoardTagMapper 和 moduleBDashBoardTagMapper。
然后,在需要注入的地方,使用@Qualifier注解指定具体的Bean名称:
例如,在某个Service中注入模块A的Mapper:
```java
@Autowired
@Qualifier("moduleADashBoardTagMapper")
private DashBoardTagMapper dashBoardTagMapperA;
```
注入模块B的Mapper:
```java
@Autowired
@Qualifier("moduleBDashBoardTagMapper")
private DashBoardTagMapper dashBoardTagMapperB;
```
注意:这样配置后,需要确保不再使用默认的Mapper扫描(或者排除这两个Mapper的自动扫描)。如果之前使用了@MapperScan扫描了这两个包,那么需要调整,避免自动创建两个同名的Bean。
如果不想移除@MapperScan,可以在@MapperScan中排除这两个Mapper,然后完全通过配置类来注册。
另一种方式:在@MapperScan中指定bean的名称生成器,使生成的Bean名称唯一(例如使用全限定类名作为Bean名称)。但是这样可能会影响其他Mapper,而且注入时也需要使用全限定类名,不太方便。
因此,推荐使用配置类手动注册的方式,这样更明确。
但是,如果两个模块中都有多个Mapper,手动注册会很麻烦。因此,可以考虑自定义Bean名称生成规则。
方式三:自定义BeanNameGenerator
创建一个自定义的BeanNameGenerator,在扫描Mapper时使用全限定类名作为Bean名称:
配置类:
```java
@Configuration
@MapperScan(basePackages = "com.huawei.libing.cloudsec.model.dao.mapper", nameGenerator = FullClassNameBeanNameGenerator.class)
public class ModuleAConfig {
}
@Configuration
@MapperScan(basePackages = "com.huawei.libing.cloudsec.whitebox.dao.mapper", nameGenerator = FullClassNameBeanNameGenerator.class)
public class ModuleBConfig {
}
```
自定义BeanNameGenerator:
```java
public class FullClassNameBeanNameGenerator extends MapperFactoryBean<Object> implements BeanNameGenerator {
@Override
public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
return definition.getBeanClassName();
}
}
```
但是,这样生成的Bean名称是全限定类名,在注入时就需要写全限定类名,不太方便。
因此,我们还可以采用另一种策略:在简单类名前加上模块名前缀(比如包名中的一段)来确保唯一性。
例如,对于包`com.huawei.libing.cloudsec.model.dao.mapper`,我们可以取包名中的`model`作为前缀,生成Bean名称为`modelDashBoardTagMapper`。
自定义BeanNameGenerator示例:
```java
public class ModulePrefixBeanNameGenerator implements BeanNameGenerator {
@Override
public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
String className = definition.getBeanClassName();
// 从包名中提取模块标识,例如:com.huawei.libing.cloudsec.model.dao.mapper -> 取'model'
// 这里简单起见,我们取包名的倒数第三部分(根据实际包结构调整)
String[] packageParts = className.split("\\.");
// 假设包结构是固定的,我们取倒数第三个(如果包结构不固定,则需要根据实际情况调整)
// 例如:com.huawei.libing.cloudsec.model.dao.mapper.DashBoardTagMapper -> 倒数第三:model
if (packageParts.length >= 3) {
String module = packageParts[packageParts.length - 3];
String simpleName = ClassUtils.getShortName(className);
// 将类名的首字母小写
simpleName = Character.toLowerCase(simpleName.charAt(0)) + simpleName.substring(1);
return module + simpleName; // 例如:modelDashBoardTagMapper
}
// 如果包名不符合预期,则使用全限定类名
return className;
}
}
```
然后在@MapperScan中指定nameGenerator:
```java
@MapperScan(basePackages = "com.huawei.libing.cloudsec.model.dao.mapper", nameGenerator = ModulePrefixBeanNameGenerator.class)
```
这样生成的Bean名称就不会冲突了。但是这种方式需要确保包结构符合预期,且自定义的生成规则能够保证唯一性。
总结:根据项目实际情况选择合适的方式。如果只有少数几个Mapper冲突,手动注册更简单;如果冲突较多,建议使用自定义BeanNameGenerator。
根据问题描述,这里只有DashBoardTagMapper冲突,因此采用手动注册的方式更直接。
下面给出手动注册的完整代码示例:
步骤1:在模块A的配置类中注册模块A的Mapper
```java
package com.huawei.libing.cloudsec.model.config;
import com.huawei.libing.cloudsec.model.dao.mapper.DashBoardTagMapper;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ModuleAConfig {
@Bean
public SqlSessionTemplate sqlSessionTemplateA(SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
@Bean
public DashBoardTagMapper moduleADashBoardTagMapper(SqlSessionTemplate sqlSessionTemplateA) {
return sqlSessionTemplateA.getMapper(DashBoardTagMapper.class);
}
}
```
步骤2:在模块B的配置类中注册模块B的Mapper
```java
package com.huawei.libing.cloudsec.whitebox.config;
import com.huawei.libing.cloudsec.whitebox.dao.mapper.DashBoardTagMapper;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ModuleBConfig {
@Bean
public SqlSessionTemplate sqlSessionTemplateB(SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
@Bean
public DashBoardTagMapper moduleBDashBoardTagMapper(SqlSessionTemplate sqlSessionTemplateB) {
return sqlSessionTemplateB.getMapper(DashBoardTagMapper.class);
}
}
```
注意:这里假设两个模块使用同一个SqlSessionFactory(通常情况)。如果使用多个数据源,则需要分别指定SqlSessionFactory。
步骤3:在需要的地方注入时使用@Qualifier
```java
@Service
public class SomeService {
private final DashBoardTagMapper dashBoardTagMapperA;
private final DashBoardTagMapper dashBoardTagMapperB;
@Autowired
public SomeService(
@Qualifier("moduleADashBoardTagMapper") DashBoardTagMapper dashBoardTagMapperA,
@Qualifier("moduleBDashBoardTagMapper") DashBoardTagMapper dashBoardTagMapperB) {
this.dashBoardTagMapperA = dashBoardTagMapperA;
this.dashBoardTagMapperB = dashBoardTagMapperB;
}
}
```
这样,两个同名的Mapper就被区分开了。
但是,如果两个模块的配置类都在同一个Spring Boot应用中,那么SqlSessionTemplate这个Bean的名称也会冲突(因为两个配置类都定义了SqlSessionTemplate的Bean,名称分别为sqlSessionTemplateA和sqlSessionTemplateB,所以不会冲突)。
另外,如果项目已经有一个SqlSessionTemplate的Bean,那么可以不用在配置类中新建,直接使用已有的。上面的例子中为了清晰,分别创建了两个SqlSessionTemplate,但实际上可以共用一个。因为这两个Mapper使用的是同一个SqlSessionFactory(同一个数据源),所以可以共用一个SqlSessionTemplate。因此,可以简化配置:
简化后的模块A配置:
```java
@Configuration
public class ModuleAConfig {
@Bean
public DashBoardTagMapper moduleADashBoardTagMapper(SqlSessionTemplate sqlSessionTemplate) {
return sqlSessionTemplate.getMapper(DashBoardTagMapper.class);
}
}
```
模块B配置同理。
这样,两个Mapper的Bean名称分别为moduleADashBoardTagMapper和moduleBDashBoardTagMapper,就避免了冲突。
最后,请确保移除了原有对这两个Mapper的自动扫描(如果之前有扫描的话),或者确保这两个Mapper没有被自动扫描(比如在@MapperScan中排除了这两个Mapper所在的包)。如果无法排除,可以在@MapperScan中指定排除这两个Mapper类(使用excludeFilters),但这样比较麻烦。因此,如果项目已经使用了@MapperScan扫描了这两个包,那么建议采用自定义BeanNameGenerator的方式,或者将这两个Mapper移动到不被扫描的包中。
如果项目允许,也可以将两个Mapper放在不同的包下,然后使用@MapperScan分别扫描并指定不同的nameGenerator,这样就不会冲突。
综上所述,我们提供了两种主要方案,请根据项目实际情况选择。
由于问题中只提到一个Mapper冲突,所以手动注册更简单直接。
因此,我们推荐使用手动注册的方式,并指定不同的Bean名称。
如果按照上述手动注册方式配置后,启动时应该不会再出现Bean名称冲突的问题。
注意:如果还有其他同名的Mapper,也需要按照同样的方式处理。
如果问题解决,请确保在代码中注入时使用正确的Bean名称。
如果问题仍然存在,请检查是否还有其他地方(比如XML配置)定义了同名的Bean。
最后,如果两个模块的配置类都在同一个Spring Boot应用中,请确保这两个配置类都被扫描到(通常在主应用的@ComponentScan范围内)。
如果两个模块是独立的,需要确保配置类在各自模块的扫描路径下。
以上解决方案应该能够解决启动报错的问题。</think>### 解决 Spring Boot 中同名 Mapper 冲突的方案
当两个模块中存在同名 Mapper 接口(`DashBoardTagMapper`)时,Spring 无法自动区分它们,导致 `ConflictingBeanDefinitionException`。以下是完整的解决方案:
#### 方案一:使用 `@Bean` + `@Qualifier` 手动注册(推荐)
```java
// 模块A配置类
@Configuration
public class ModuleAConfig {
@Bean("modelDashBoardTagMapper")
public DashBoardTagMapper modelDashBoardTagMapper(
SqlSessionTemplate sqlSessionTemplate) {
return sqlSessionTemplate.getMapper(
com.huawei.libing.cloudsec.model.dao.mapper.DashBoardTagMapper.class
);
}
}
// 模块B配置类
@Configuration
public class ModuleBConfig {
@Bean("whiteboxDashBoardTagMapper")
public DashBoardTagMapper whiteboxDashBoardTagMapper(
SqlSessionTemplate sqlSessionTemplate) {
return sqlSessionTemplate.getMapper(
com.huawei.libing.cloudsec.whitebox.dao.mapper.DashBoardTagMapper.class
);
}
}
```
**使用方式:**
```java
@Service
public class DashboardService {
// 通过限定符注入
@Autowired
@Qualifier("modelDashBoardTagMapper")
private DashBoardTagMapper modelMapper;
@Autowired
@Qualifier("whiteboxDashBoardTagMapper")
private DashBoardTagMapper whiteboxMapper;
public void processData() {
// 使用model模块的Mapper
modelMapper.selectTags();
// 使用whitebox模块的Mapper
whiteboxMapper.selectTags();
}
}
```
#### 方案二:使用自定义 `BeanNameGenerator`(适用于多个同名 Mapper)
```java
// 自定义Bean名称生成器
public class UniqueMapperBeanNameGenerator extends ClassNameBeanNameGenerator {
@Override
public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
// 使用全限定类名作为Bean名称
return definition.getBeanClassName();
}
}
```
```java
// 主配置类
@SpringBootApplication
@MapperScan(
basePackages = "com.huawei.libing.cloudsec.model.dao.mapper",
nameGenerator = UniqueMapperBeanNameGenerator.class
)
@MapperScan(
basePackages = "com.huawei.libing.cloudsec.whitebox.dao.mapper",
nameGenerator = UniqueMapperBeanNameGenerator.class
)
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
```
**使用方式:**
```java
@Service
public class DashboardService {
// 使用全限定类名注入
@Autowired
@Qualifier("com.huawei.libing.cloudsec.model.dao.mapper.DashBoardTagMapper")
private DashBoardTagMapper modelMapper;
@Autowired
@Qualifier("com.huawei.libing.cloudsec.whitebox.dao.mapper.DashBoardTagMapper")
private DashBoardTagMapper whiteboxMapper;
}
```
### 解决方案对比
| **方案** | **适用场景** | **优点** | **缺点** |
|---------|------------|---------|---------|
| `@Bean` + `@Qualifier` | 少量同名Mapper | 配置简单,直观易理解 | 每个Mapper需手动注册 |
| 自定义 `BeanNameGenerator` | 多个同名Mapper | 自动处理所有Mapper,减少配置 | 注入时需使用全限定类名 |
### 关键点解释
1. **`SqlSessionTemplate` 的作用**:
- MyBatis 通过 `SqlSessionTemplate` 创建 Mapper 接口的代理实例
- `getMapper()` 方法根据接口类动态生成实现
2. **`@Qualifier` 的必要性**:
- 当存在多个同类型 Bean 时,必须使用 `@Qualifier` 指定具体实例
- 替代方案:使用 `@Resource(name = "beanName")`
3. **避免自动扫描冲突**:
- 如果使用方案一,需在 `@SpringBootApplication` 中排除自动扫描:
```java
@SpringBootApplication(exclude = {
MapperScannerRegistrar.class,
AutoConfiguredMapperScannerRegistrar.class
})
```
### 完整配置示例(方案一)
```java
// 主应用入口
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
// 模块A配置
@Configuration
@MapperScan(
basePackages = "com.huawei.libing.cloudsec.model.dao.mapper",
excludeFilters = @ComponentScan.Filter(
type = FilterType.ASSIGNABLE_TYPE,
classes = DashBoardTagMapper.class
)
)
public class ModuleAConfig {
@Bean("modelDashBoardTagMapper")
public DashBoardTagMapper modelDashBoardTagMapper(SqlSessionTemplate sqlSessionTemplate) {
return sqlSessionTemplate.getMapper(
com.huawei.libing.cloudsec.model.dao.mapper.DashBoardTagMapper.class
);
}
}
// 模块B配置
@Configuration
@MapperScan(
basePackages = "com.huawei.libing.cloudsec.whitebox.dao.mapper",
excludeFilters = @ComponentScan.Filter(
type = FilterType.ASSIGNABLE_TYPE,
classes = DashBoardTagMapper.class
)
)
public class ModuleBConfig {
@Bean("whiteboxDashBoardTagMapper")
public DashBoardTagMapper whiteboxDashBoardTagMapper(SqlSessionTemplate sqlSessionTemplate) {
return sqlSessionTemplate.getMapper(
com.huawei.libing.cloudsec.whitebox.dao.mapper.DashBoardTagMapper.class
);
}
}
```
### 常见问题排查
1. **出现 `NoSuchBeanDefinitionException`**:
- 检查配置类是否被扫描到(确保在 `@ComponentScan` 范围内)
- 确认 `SqlSessionTemplate` Bean 已正确配置
2. **出现 `BeanCreationException`**:
- 检查 Mapper 接口路径是否正确
- 确认 MyBatis 已正确配置(检查 `@MapperScan` 或 `mybatis.mapper-locations`)
3. **SQL 方法未实现错误**:
- 确认 Mapper XML 文件在 `classpath*:/mapper/**/*.xml` 路径下
- 检查 XML 中的 namespace 与接口全限定名一致
阅读全文