spring-cloud-task icon indicating copy to clipboard operation
spring-cloud-task copied to clipboard

Duplicated transaction manager

Open kewne opened this issue 3 years ago • 6 comments
trafficstars

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

kewne avatar Feb 06 '22 16:02 kewne

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() {

    }
}

cppwfs avatar Feb 07 '22 18:02 cppwfs

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.

kewne avatar Feb 08 '22 09:02 kewne

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.

cppwfs avatar Feb 08 '22 12:02 cppwfs

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:

  1. 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";
  2. 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".

kewne avatar Feb 08 '22 13:02 kewne

Thank you @kewne for your issue and the thought that went into it.

  1. 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.
  2. 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.

cppwfs avatar Feb 08 '22 14:02 cppwfs

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?

ankushthakur2593 avatar Sep 27 '22 08:09 ankushthakur2593

This issue was resolved with the following PR https://github.com/spring-cloud/spring-cloud-task/pull/858

cppwfs avatar Feb 16 '23 22:02 cppwfs

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..

Manu10744 avatar May 15 '23 15:05 Manu10744

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 avatar Oct 20 '23 12:10 Manu10744

@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 avatar Oct 20 '23 15:10 kewne

@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?

Manu10744 avatar Oct 20 '23 15:10 Manu10744

@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.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?

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?

AhHa45 avatar Nov 13 '23 12:11 AhHa45

Closed via https://github.com/spring-cloud/spring-cloud-task/pull/934

cppwfs avatar Nov 17 '23 15:11 cppwfs