作者:azzlle
时间:2026年4月15日
关键词:Spring Boot, MyBatis Plus, 多数据源, @Resource注入, Bean冲突
一、问题背景
在采用多数据源架构:传统MySQL数据库用于业务数据(MGR数据源),Doris数据库用于大数据分析(MPP数据源)。项目使用Spring Boot + MyBatis Plus框架。
最近在重构过程中,新增了Doris数据源的Mapper接口DorisBaseMapper,但应用启动时频繁报错,无法注入正确的Bean。
二、错误现象
应用启动时抛出以下异常:
Description: The bean 'bankCustMapper' could not be injected because it is a JDK dynamic proxy The bean is of type 'jdk.proxy2.$Proxy254' and implements: com.example.project.service.mapper.mgr.CustomMapper Expected a bean of type 'com.example.project.service.mapper.mpp.DorisBaseMapper' which implements: com.baomidou.mybatisplus.core.mapper.BaseMapper Action: Consider injecting the bean as one of its interfaces or forcing the use of CGLib-based proxies by setting proxyTargetClass=true on @EnableAsync and/or @EnableCaching.关键信息提取:
- 期望类型:
DorisBaseMapper(继承BaseMapper) - 实际类型:
CustomMapper的JDK动态代理($Proxy254) - Bean名称:
bankCustMapper
三、深层分析:@Resource按名称注入的陷阱
经过初步排查,确认问题并非简单的XML配置错误或MyBatis映射问题。核心矛盾点在于Spring Bean注入机制中的名称匹配规则。
3.1 检查Service实现类
@Service @RequiredArgsConstructor @ConditionalOnExpression("'${" + DorisDataSourceProp.DORIS_ENABLE_PROPERTIES_KEY + "}'.equals('true')") public class DorisBaseServiceImpl implements DorisBaseService { @Resource // 关键点:使用@Resource按名称注入 private final DorisBaseMapper bankCustMapper; // ... }3.2 Spring Bean注册情况
| Bean类型 | 默认Bean名称 | 实现接口 | 数据源 |
|---|---|---|---|
CustomMapper | bankCustMapper | 自定义接口 | MGR数据源 |
DorisBaseMapper | dorisBaseMapper | 继承BaseMapper | Doris数据源 |
3.3 @Resource注解的行为分析
@Resource注解的注入规则:
- 默认按名称匹配:先按字段名
bankCustMapper查找对应Bean - 名称匹配失败时:再按类型匹配
- 名称匹配成功:即使类型不兼容也会注入,导致类型转换异常
关键发现:字段名bankCustMapper正好匹配到了CustomMapper接口的Bean,而不是期望的DorisBaseMapper。
3.4 方法签名重叠(加剧问题)
// CustomMapper.java(MGR数据源) List<PersonalCustomerVO> listPrivateCustomers(PersonalCustParam customerQueryParam); // DorisBaseMapper.java(Doris数据源) List<PersonalCustomerVO> listPrivateCustomers(PersonalCustParam customerQueryParam); // 同名方法同名方法的存在使得两个Mapper接口在某种程度上"相似",但它们的继承关系不同:
CustomMapper:自定义接口,无基类DorisBaseMapper:继承BaseMapper<BaseEntity>
四、根本原因:@Resource按名称注入的陷阱
4.1 注入流程分析
@Resource private final DorisBaseMapper bankCustMapper;Spring容器的Bean查找过程:
- 查找名称为
bankCustMapper的Bean → 找到CustomMapper的实例 - 尝试将
CustomMapper实例赋值给DorisBaseMapper类型的字段 - 类型不兼容:
CustomMapper没有实现BaseMapper接口 - 抛出异常:"Expected a bean of type 'DorisBaseMapper' which implements BaseMapper"
4.2 多数据源配置的复杂性
项目中有两套数据源配置:
// MGR数据源配置 @Configuration @MapperScan(basePackages = "com.example.project.service.mapper.mgr") public class DataSourceConfig { /* ... */ } // Doris数据源配置 @Configuration @ConditionalOnExpression("'${project.mpp.jdbc.enable}'.equals('true')") @MapperScan(basePackages = "com.example.project.service.mapper.mpp") public class MppDataSourceConfig { /* ... */ }当Doris数据源启用时(project.mpp.jdbc.enable=true),两个数据源配置同时生效,导致两个同名的Bean同时存在。
五、解决方案
方案一:使用@Qualifier明确指定Bean(推荐)
@Service @RequiredArgsConstructor @ConditionalOnExpression("'${" + DorisDataSourceProp.DORIS_ENABLE_PROPERTIES_KEY + "}'.equals('true')") public class DorisBaseServiceImpl implements DorisBaseService { @Resource @Qualifier("dorisBaseMapper") // 明确指定Bean名称 private final DorisBaseMapper bankCustMapper; // ... }方案二:使用@Autowired按类型注入
@Autowired // 改为按类型注入 private final DorisBaseMapper bankCustMapper;方案三:统一字段命名规范
// 将字段名改为与Bean名称一致 @Resource private final DorisBaseMapper dorisBaseMapper; // 字段名与Bean名称一致