more than one ‘primary‘ bean found among candidates: xxxTransactionManager

目录

    • 背景
    • 报警内容
    • 思考过程
    • 日志验证
    • 解决办法
    • 探究SpringBoot自动装配事务管理器
        • 如何装配自己的多个事务管理器

背景

详细内容可看我的上一篇文章 springboot 使用多数据源 + 多事务管理器
当我配置多个事务管理器,并且在某个serviceImpl类的方法上使用 @Transactional() 注解的时候,出现了文章标题的报错。


报警内容

org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 
'org.springframework.transaction.TransactionManager' available: 
more than one 'primary' bean found among candidates: [xxxxTransactionManager, xxxxTransactionManager, transactionManager]

思考过程

首先,报错意思很明显,spring 发现有多个 primary bean, 这些bean都是事务管理器的bean。 但是我自己其实只注册了两个 事务管理器,
且 我已经标注了其中一个作为我的 @primary bean, 那么为何报错最后多出了一个 transactionManager
多半是基础框架里面设置了基础的事务管理器,那我就得从日志去看下


日志验证

上面找到了大概的方向,这次我再用启动日志去观察下 是否真的如此、
在idea启动类配置上加上 debug=true;logging.level.root=debug
然后启动服务,观察日志输出

 DataSourceTransactionManagerAutoConfiguration.DataSourceTransactionManagerConfiguration#transactionManager:
      Did not match:
         - @ConditionalOnMissingBean (types: org.springframework.transaction.PlatformTransactionManager; SearchStrategy: all) found beans of type 'org.springframework.transaction.PlatformTransactionManager' 
         - bigdataTransactionManager, jxTransactionManager, transactionManager (OnBeanCondition)

这里可以看到在 spring的事务管理器的自动装配的时候,已经发现了三个事务管理器,我自己注册的两个,还有一个是不知道哪儿来的。
这里我想到一个办法,既然知道了你的beanName,那么我就在我把我自己注册的其中一个事务管理器改成你这个名字, 日志总会打印出我覆盖你的信息,这样的话就能知道大概是哪里装配的那个 transactionManager事务管理器了。

    @Bean(name = "transactionManager")
    @Primary
    public DataSourceTransactionManager bigdataTransactionManager() {
        return new DataSourceTransactionManager(bigdataDataSource());
    }

修改以后,我重新启动服务,再次观察日志, 找到一段 关键信息

Overriding bean definition for bean 'transactionManager' with a different definition: 
replacing [Root bean: class [null]; scope=; abstract=false; lazyInit=null; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=true; factoryBeanName=bigdataDataSourceConfig; 
factoryMethodName=bigdataTransactionManager; initMethodName=null; destroyMethodName=(inferred); 
defined in class path resource [xxxx/xxx/xxxx/jdbc/BigdataDataSourceConfig.class]] 
with [Root bean: class [null]; scope=; abstract=false; lazyInit=null; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=true; 
factoryBeanName=xxxx.xxx.xxx.jdbc.MybatisPlusConfiguration; 
factoryMethodName=transactionManager; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [xx/xxx/xxx/jdbc/MybatisPlusConfiguration.class]]

这里我是把他的bean 覆盖了,原始的bean是从 MybatisPlusConfiguration这个类来的。 全局搜索了一下 找到这个类之后,发现了问题所在

@Bean(name = "transactionManager")
    @Primary
    public DataSourceTransactionManager transactionManager(DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

这里他生成了这个 事务管理器,并且把这个管理器也置为 Primary。 所以springboot才会报错说,有多个primary bean存在。


解决办法

找到问题所在就简单了,因为没有使用到 mybatisplus 所以只需要找到这个依赖在哪儿引入的, 去除掉之后就好了。
idea里面可以装 Maven Helper 插件,这个插件可以很好的帮助定位 依赖从哪儿来。

或者不想改 pom.xml 也可以, 直接像我那样用自己配置的 事务管理器把他的覆盖掉 哈哈哈


探究SpringBoot自动装配事务管理器

@SpringBootApplication 注解开始 往上找

  1. @SpringBootApplication --> @EnableAutoConfiguration --> @AutoConfigurationPackage
    找到 @AutoConfigurationPackage 所在的包路径,点击跳转过去。
    more than one ‘primary‘ bean found among candidates: xxxTransactionManager_第1张图片

这个包目录下,我们可以很容易地找到一个子路径 : transaction
里面可以很容易找到一个类 TransactionAutoConfiguration, 很明显,这个类就是spring用来自动装载事务管理器的类。

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(PlatformTransactionManager.class)
@AutoConfigureAfter({ JtaAutoConfiguration.class, HibernateJpaAutoConfiguration.class,
		DataSourceTransactionManagerAutoConfiguration.class, Neo4jDataAutoConfiguration.class })
@EnableConfigurationProperties(TransactionProperties.class)
public class TransactionAutoConfiguration {

	@Bean
	@ConditionalOnMissingBean
	public TransactionManagerCustomizers platformTransactionManagerCustomizers(
			ObjectProvider<PlatformTransactionManagerCustomizer<?>> customizers) {
		return new TransactionManagerCustomizers(customizers.orderedStream().collect(Collectors.toList()));
	}
	..........
}

进去之后看这个类的注解, 重点在 @AutoConfigureAfter , 这个注解的意思就是 应在其他指定的自动配置类之后应用自动配置。
所以根据这个注解里面的类 我们又找到了 DataSourceTransactionManagerAutoConfiguration这个类

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ JdbcTemplate.class, PlatformTransactionManager.class })
@AutoConfigureOrder(Ordered.LOWEST_PRECEDENCE)
@EnableConfigurationProperties(DataSourceProperties.class)
public class DataSourceTransactionManagerAutoConfiguration {

	@Configuration(proxyBeanMethods = false)
	@ConditionalOnSingleCandidate(DataSource.class)
	static class DataSourceTransactionManagerConfiguration {

		@Bean
		@ConditionalOnMissingBean(PlatformTransactionManager.class)
		DataSourceTransactionManager transactionManager(DataSource dataSource,
				ObjectProvider<TransactionManagerCustomizers> transactionManagerCustomizers) {
			DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(dataSource);
			transactionManagerCustomizers.ifAvailable((customizers) -> customizers.customize(transactionManager));
			return transactionManager;
		}
	}
}

这里面我们可以看到,生成了一个Bean就是 transactionManager。 所以文章开头提到的那个多余的 bean 就是这里出现的。

题外话:
刚才我们在 TransactionAutoConfiguration 类 里面的 @AutoConfigureAfter 注解里面还看到一个类,Neo4jDataAutoConfiguration,看他的代码可以发现,里面也会注册一个事务管理器,但是自动装配的时候只会有一个事务管理器。这是因为:

  1. 这两个Bean上都有一个注解, @ConditionalOnMissingBean(PlatformTransactionManager.class)
    2.DataSourceTransactionManager 和 Neo4jTransactionManager 都是 PlatformTransactionManager 的一个子类,所以beanFactory里面只要加载了其中一个bean 就不会加载另一个了。

如何装配自己的多个事务管理器

在springboot的类加载流程中,它首先会加载应用程序配置的类(也就是你自己的config类),然后再去加载那些自动配置类。所以当我们配置了事务配置的类,就会优先加载我们自己设置的bean。

    @Bean(name = "transactionManager")
    @Primary
    public DataSourceTransactionManager bigdataTransactionManager() {
        return new DataSourceTransactionManager(bigdataDataSource());
    }
    
    @Bean(name = "anTransactionManager")
    public DataSourceTransactionManager anTransactionManager() {
        return new DataSourceTransactionManager(anDataSource());
    }

并且这个bean的类是 接口PlatformTransactionManager 的实现类。那么spring在之后解析spring.factories文件中的自动事务配置类时,会因为@ConditionalOnMissingBean(PlatformTransactionManager.class)的限制,就会跳过解析。也就完成了自己的事务管理器的注入

你可能感兴趣的:(java,SpringBoot,java,spring,boot,spring)