4

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?

achingfingers
  • 1,827
  • 5
  • 24
  • 48
  • 1
    Ditch the `` and `` and remove `transaction-manager="transactionManager"` from ``. Instead use `@Transactional` everywhere and specify which transaction manager to use on it those `@Transactional` beans. – M. Deinum Sep 05 '14 at 11:29
  • 1
    I found that an ugly solution, because i tie my modules to a specific TransactionManager name. It has been working before, so it should now ... there must be another way! – achingfingers Sep 05 '14 at 11:33
  • So you don't mind binding it to a specific entitymanager but a tx manazger is a problem?. With `@Transactional` you need to specify which to use, in case of multiple managers. If you don't use multiple `` bllocks. – M. Deinum Sep 05 '14 at 11:35
  • 1
    Maybe I am a bit picky ... I will try it out! Still it looks ugly to me to add a name to every single @Transactional annotation in the codebase (which means refactoring nearly one hundred classes) – achingfingers Sep 05 '14 at 11:37
  • It's ugly indeed. I think you must have something wrong in your configuration because I remember working with two separate persistence contexts without a problem. I'll try to find it and come back with an answer. – Repoker Sep 05 '14 at 11:38
  • This might help: http://stackoverflow.com/questions/15008809/multiple-jparepositories-in-xml-config-how-to-configure-with-enablejpareposit/19976132#19976132 – Steve Sep 05 '14 at 11:42
  • @M.Deinum I finally chose your solution. If you post an answer I am going to accept it! – achingfingers Sep 05 '14 at 12:56

1 Answers1

1

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.

Repoker
  • 202
  • 3
  • 12
  • That's exactly what I've been doing. Just ommitted the persistence.xml in my question. The same configuration i am having now is up and running in a command line app. That's why I think it might be a spring boot config issue ... Thank you anyway! – achingfingers Sep 05 '14 at 12:29