spring-cloud-task
spring-cloud-task copied to clipboard
Duplicated transaction manager
In https://github.com/spring-cloud/spring-cloud-task/commit/00b8acab5530779a811613e46f8af17a5fc1d3e1#diff-1ebccb318e2d6a373043c744ca37eb7e8048d8130765f8904184795a593d5a89L92, @ConditionalOnMissingBean was removed from the springCloudTaskTransactionManager bean, which means that it will conflict with any other AutoConfiguration that creates a PlatformTransactionManager bean (such as JPA/Hibernate).
This repository contains a project that reproduces the error: https://github.com/kewne/spring-cloud-issue-repro;
trying to launch com.example.demo.DemoApplication fails with
java.lang.IllegalStateException: Failed to execute CommandLineRunner
at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:772) ~[spring-boot-2.6.3.jar:2.6.3]
at org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:753) ~[spring-boot-2.6.3.jar:2.6.3]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:309) ~[spring-boot-2.6.3.jar:2.6.3]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1303) ~[spring-boot-2.6.3.jar:2.6.3]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1292) ~[spring-boot-2.6.3.jar:2.6.3]
at com.example.demo.DemoApplication.main(DemoApplication.java:14) ~[classes/:na]
Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'org.springframework.transaction.TransactionManager' available: expected single matching bean but found 2: transactionManager,springCloudTaskTransactionManager
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveNamedBean(DefaultListableBeanFactory.java:1271) ~[spring-beans-5.3.15.jar:5.3.15]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveBean(DefaultListableBeanFactory.java:494) ~[spring-beans-5.3.15.jar:5.3.15]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:349) ~[spring-beans-5.3.15.jar:5.3.15]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:342) ~[spring-beans-5.3.15.jar:5.3.15]
at org.springframework.transaction.interceptor.TransactionAspectSupport.determineTransactionManager(TransactionAspectSupport.java:503) ~[spring-tx-5.3.15.jar:5.3.15]
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:342) ~[spring-tx-5.3.15.jar:5.3.15]
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) ~[spring-tx-5.3.15.jar:5.3.15]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.15.jar:5.3.15]
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:753) ~[spring-aop-5.3.15.jar:5.3.15]
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:698) ~[spring-aop-5.3.15.jar:5.3.15]
at com.example.demo.DemoService$$EnhancerBySpringCGLIB$$318b617c.foo(<generated>) ~[classes/:na]
at com.example.demo.DemoApplication.lambda$runner$0(DemoApplication.java:19) ~[classes/:na]
at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:769) ~[spring-boot-2.6.3.jar:2.6.3]
... 5 common frames omitted
In your stack trace there are more than one PlatformTransactionManger that was found. In this case you need to specify which transaction manager you want to use. For example:
@Service
public class DemoService {
@Transactional("transactionManager")
public void foo() {
}
}
I understood that's the issue, what I find weird is why Spring Cloud Task creates a new PlatformTransactionManager bean itself when, from what I could understand, it reuses an existing datasource;
IMO it could also reuse an existing transaction manager (and it seems to do so, in certain situations).
In effect, the current behavior forces code to be explicit about the transaction manager to use everywhere simply because one pulled Spring Cloud Task.
There's also the case where, if one creates a TaskConfigurer that returns an already existing PlatformTransactionManager, you still get the same error because apparently the same object is registered as two different beans.
In this case it was to allow task to utilize its own transaction manager seperate from those that may be provided by the user.
More can be read about this issue from the original case: https://github.com/spring-cloud/spring-cloud-task/issues/652 and the subsequent PR that resolved it.
Shouldn't the transaction manager be provided by the TaskConfigurer, though, in which case also exposing it as a bean is unnecessary?
My points here are that:
- if you use Spring's transactional features as "normal", pulling in Spring Cloud Task breaks your setup; in particular, if you're using Boot and not even customizing anything, it seems like a reasonable expectation that things should "just work";
- there's nothing in the docs that even suggests one might run into this issue (I had to resort to looking through source code and github issues and PRs); the generic error message is also not very helpful.
Another alternative would be to add @ConditionalOnMissingBean(names = "springCloudTaskTransactionManager"), which is easy to document as "if you want to reuse a TransactionManager, add 'springCloudTaskTransactionManager' as an alias".
Thank you @kewne for your issue and the thought that went into it.
- In this case Spring Cloud Task needs to respect the transaction managers that are provided by other starters and their settings that maybe different than those needed by task.
- Agreed this issue is not represented in the documentation and needs to be added. I'll update the label for this issue and add the docs.
I'm also facing the same issue and as a workaround to unblock, I've mentioned which transaction manager I want to use but there are too many files in my project which are using this annotation.
I've noticed that the solution for this issue is merged as part of this PR. @cppwfs - Could you please share a tentative release date for the same?
This issue was resolved with the following PR https://github.com/spring-cloud/spring-cloud-task/pull/858
I'm using spring-cloud-starter-task version 3.0.2 and still get this error no matter if i specify spring.cloud.task.transaction-manager or not.
I have to inject a PlatformTransactionManager in my step methods and it always says it finds two..
Any solution to this?
I'm still having two transaction managers only because I have Spring Cloud Dependencies in my app. This breaks all of my @Transactional methods.
@Manu10744 AFAICT #858 was apparently never merged and, looking at the current code, it doesn't look like it was cherry-picked at all, so the issue still exists.
@kewne I think it is because take a look at https://github.com/fmbenhassine/spring-cloud-task/blob/e4581420b04e872f8aac4cb3ad8bab62bbe9c941/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/SimpleTaskAutoConfiguration.java
The TransactionManager Bean is annotated with Conditional(NoTransactionManagerProperty.class)
So, i am 100% sure I have this property in my application.yml. So why is this Bean still getting created??
@cppwfs Could you please explain?
@kewne I think it is because take a look at fmbenhassine/spring-cloud-task@
e458142/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/SimpleTaskAutoConfiguration.javaThe TransactionManager Bean is annotated with
Conditional(NoTransactionManagerProperty.class)So, i am 100% sure I have this property in my
application.yml. So why is this Bean still getting created??@cppwfs Could you please explain?
I have the same issue using spring boot 2.7.16 and spring-cloud-task 2.4.6 any news on how to fix this without upgrading?
Closed via https://github.com/spring-cloud/spring-cloud-task/pull/934