achingfingers
achingfingers

Reputation: 1936

Spring fails to choose between multiple TransactionManager beans

I migrating my spring batch processes from a command line application to a spring boot webapp including the spring-batch-admin-manager (version 1.3.0).

My old command-line app worked with two JPA databases and two TransactionManager instances. I remember it has been a hell of configuration to get that running. I expected things to get more comfortable, when starting with Spring Boot, but now things seem to be even worse:

Spring is unable to choose the right TransactionManager instance for transactions.

On application startup Spring is visiting one of my classes to execute a @PostConstruct annotated code block, from where a transactional method should be called.

@PostConstruct
public void init() {
  eventType = eventTypeBo.findByLabel("Sport"); // <-- Calling transactional method
  //...
}

From here the error is thrown. Have a look at the stacktrace:

Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: 
           No qualifying bean of type [org.springframework.transaction.PlatformTransactionManager] is defined: 
           expected single matching bean but found 2: transactionManager,osm.transactionManager
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:313)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.determineTransactionManager(TransactionAspectSupport.java:337)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:252)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:95)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:207)
    at com.sun.proxy.$Proxy89.findByLabel(Unknown Source)
    at com.company.model.service.impl.EventTypeBo.findByLabel(EventTypeBo.java:43)
    at com.company.batch.article.utils.converter.SomeConverter.init(SomeConverter.java:83)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    //...

As you can see from the error log my TransactionManagers are named "transactionManager" and "osm.transactionManager". Transactions are configured accordingly:

<!-- DATABASE 1 -->
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
    <property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>
<tx:annotation-driven transaction-manager="transactionManager" />

<!-- DATABASE 2 -->
<bean id="osm.transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
    <property name="entityManagerFactory" ref="osm.entityManagerFactory" />
</bean>
<tx:advice id="txAdvice" transaction-manager="osm.transactionManager">
    <tx:attributes>
        <tx:method name="*" />
    </tx:attributes>
</tx:advice>
<aop:config>
    <aop:pointcut id="osmServiceOperation"
        expression="execution(* com.qompa.osm.service.spec..*Service.*(..))" />
    <aop:advisor advice-ref="txAdvice" pointcut-ref="osmServiceOperation" />
</aop:config>

I am using two different GenericDao implementions to distinguish between PersistenceContexts when accessing data:

public class OsmGenericDao<T> implements IGenericDao<T> {

  @PersistenceContext(unitName = "osm.entityManagerFactory")
  protected EntityManager em;
  //...
}

public class GenericDao<T> implements IGenericDao<T> {

  @PersistenceContext(unitName = "entityManagerFactory")
  protected EntityManager em;
  //...
}

Everything seems to be fproperly configured: But still it fails!

EDIT: As far as I can follow Spring's TransactionAspectSupport tries to determine the transactionmanager via a qualifier (determineTransactionManager). Because the findByLabel's @Transactional annotation has no qualifier it tries to determine the correct bean with help of DefaultListableBeanFactory"s getBean method, where two beans of the same type are found that can't be further distinguished.

There must be a way to tell Spring to choose the transactionManager according to the persistence context!

Any ideas?

Upvotes: 4

Views: 10101

Answers (1)

Repoker
Repoker

Reputation: 212

Here's my working configuration with 2 persistence contexts:

<!-- METADATA -->

<!-- Metadata connection pool -->
<bean id="scprMetadataDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
    destroy-method="close">
    <property name="driverClass"
        value="..." />
    <property name="jdbcUrl"
        value="..." />
    <property name="user"
        value="..." />
    <property name="password"
        value="..." />
    <property name="maxPoolSize"
        value="..." />
    <property name="minPoolSize"
        value="..." />
</bean>

<!-- Metadata entity manager factory -->
<bean id="scprMetadataEntityManagerFactory"
    class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="jpaVendorAdapter">
        <bean
            class="org.springframework.orm.jpa.vendor.EclipseLinkJpaVendorAdapter">
            <property name="database" value="H2" />
        </bean>
    </property>
</bean>

<!-- Metadata transaction manager -->
<bean id="scprMetadataTransactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
    <property name="entityManagerFactory" ref="scprMetadataEntityManagerFactory" />
</bean>


<!-- DOMAIN -->

<!-- Domain connection pool -->
<bean id="scprDomainDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
    destroy-method="close">
    <property name="driverClass"
        value="..." />
    <property name="jdbcUrl"
        value="..." />
    <property name="user"
        value="..." />
    <property name="password"
        value="..." />
    <property name="maxPoolSize"
        value="..." />
    <property name="minPoolSize"
        value="..." />
</bean>

<!-- Domain entity manager factory -->
<bean id="scprDomainEntityManagerFactory"
    class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="jpaVendorAdapter">
        <bean
            class="org.springframework.orm.jpa.vendor.EclipseLinkJpaVendorAdapter">
            <property name="database" value="SQL_SERVER" />
        </bean>
    </property>
</bean>

<!-- Domain transaction manager -->
<bean id="scprDomainTransactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
    <property name="entityManagerFactory" ref="scprDomainEntityManagerFactory" />
</bean>

Other config (missing in your question):

<persistence-unit name="scpr_metadata" transaction-type="RESOURCE_LOCAL">
    <exclude-unlisted-classes>true</exclude-unlisted-classes>
    <class>...</class>
    <class>...</class>
    <class>...</class>
</persistence-unit>

<persistence-unit name="scpr_domain" transaction-type="RESOURCE_LOCAL">
    <exclude-unlisted-classes>true</exclude-unlisted-classes>
    <class>...</class>
    <class>...</class>
    <class>...</class>
</persistence-unit>

And in my java class:

@Repository
public final class MetadataRepositoryImpl implements MetadataRepository {

@PersistenceContext(unitName = "scpr_metadata")
private EntityManager em;

And:

@Repository
public final class DomainRepositoryImpl implements DomainRepository {

@PersistenceContext(unitName = "scpr_domain")
private EntityManager em;

Hope this helps you.

Upvotes: 1

Related Questions