Working with Spring Data JPA, Hibernate and multiple transaction manager: No bean named 'transactionManager' is defined
Asked Answered
E

6

13

EDIT: to whoever may be in interested in this issue, I provide the analysys of the problem with the related solution at the end of the question.

I am configuring a module for a web application in which I am using Spring 3.2, Hibernate 4.1, Spring Data JPA 1.3 and Apache CXF 2.5 (in particular the JAX-RS module). I have the following configuration (which is working perfectly fine, detailed are omitted for sake of conciseness):

  @Bean(name = "entityManagerFactory")
  public LocalContainerEntityManagerFactoryBean getEntityManagerFactory() throws SQLException{
    LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
    //...    
    return factory;
  }

  @Bean(name = "transactionManager")
  public JpaTransactionManager getTransactionManager() throws SQLException{
    JpaTransactionManager manager = new JpaTransactionManager();
    //...    
    return manager;
  }

  @Bean(name = "persistenceExceptionTranslator")
  public PersistenceExceptionTranslator getPersistenceExceptionTranslator(){
    return new HibernateExceptionTranslator();
  }

My problem is that I have to rely on some external modules which define their own PlatformTransactionManager, so I find myself working with more transaction manager at the same time. This issue is easily addressed by Transactional.html#value(), so wherever I need to use @Transactional I qualified the annotation with the name of the transaction manager I have to use for that transaction.
I would like to change the name of the transaction manager I define in my module to something more meaningful, to meet the standard of the external modules. So, e.g., externalModule1 defines its manager as externalModule1TransactionManager and I would like to have

  @Bean(name = "myModuleransactionManager")
  public JpaTransactionManager getTransactionManager() throws SQLException{
    JpaTransactionManager manager = new JpaTransactionManager();
    //...    
    return manager;
  }

This seems pretty easy, unfortunately when I do this change (and I change the usage of @Transactional#value() accordingly I get an exception.

java.lang.RuntimeException: org.apache.cxf.interceptor.Fault: No bean named 'transactionManager' is defined
    at org.apache.cxf.interceptor.AbstractFaultChainInitiatorObserver.onMessage(AbstractFaultChainInitiatorObserver.java:110)
    at org.apache.cxf.phase.PhaseInterceptorChain.doIntercept(PhaseInterceptorChain.java:323)
    at org.apache.cxf.transport.ChainInitiationObserver.onMessage(ChainInitiationObserver.java:123)
    at org.apache.cxf.transport.http.AbstractHTTPDestination.invoke(AbstractHTTPDestination.java:207)
    at org.apache.cxf.transport.servlet.ServletController.invokeDestination(ServletController.java:213)
    at org.apache.cxf.transport.servlet.ServletController.invoke(ServletController.java:154)
    at org.apache.cxf.transport.servlet.CXFNonSpringServlet.invoke(CXFNonSpringServlet.java:126)
    at org.apache.cxf.transport.servlet.AbstractHTTPServlet.handleRequest(AbstractHTTPServlet.java:185)
    at org.apache.cxf.transport.servlet.AbstractHTTPServlet.doGet(AbstractHTTPServlet.java:113)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:621)
    at org.apache.cxf.transport.servlet.AbstractHTTPServlet.service(AbstractHTTPServlet.java:164)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:305)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:222)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:123)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:472)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:171)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:99)
    at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:936)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:407)
    at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1004)
    at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:589)
    at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:312)
    at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:895)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:918)
    at java.lang.Thread.run(Thread.java:662)
Caused by: org.apache.cxf.interceptor.Fault: No bean named 'transactionManager' is defined
    at org.apache.cxf.service.invoker.AbstractInvoker.createFault(AbstractInvoker.java:155)
    at org.apache.cxf.service.invoker.AbstractInvoker.invoke(AbstractInvoker.java:121)
    at org.apache.cxf.jaxrs.JAXRSInvoker.invoke(JAXRSInvoker.java:167)
    at org.apache.cxf.jaxrs.JAXRSInvoker.invoke(JAXRSInvoker.java:94)
    at org.apache.cxf.interceptor.ServiceInvokerInterceptor$1.run(ServiceInvokerInterceptor.java:58)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:439)
    at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:303)
    at java.util.concurrent.FutureTask.run(FutureTask.java:138)
    at org.apache.cxf.workqueue.SynchronousExecutor.execute(SynchronousExecutor.java:37)
    at org.apache.cxf.interceptor.ServiceInvokerInterceptor.handleMessage(ServiceInvokerInterceptor.java:106)
    at org.apache.cxf.phase.PhaseInterceptorChain.doIntercept(PhaseInterceptorChain.java:263)
    ... 25 more
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'transactionManager' is defined
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanDefinition(DefaultListableBeanFactory.java:568)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getMergedLocalBeanDefinition(AbstractBeanFactory.java:1099)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:278)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:198)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.determineTransactionManager(TransactionAspectSupport.java:246)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:100)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:155)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    at org.springframework.data.jpa.repository.support.LockModeRepositoryPostProcessor$LockModePopulatingMethodIntercceptor.invoke(LockModeRepositoryPostProcessor.java:92)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:91)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:155)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:204)
    at sun.proxy.$Proxy98.save(Unknown Source)
    at myModule.package.SomeOtherClass.someOtherMethod(SomeOtherClass.java:114)
    at myModule.package.SomeOtherClass$$FastClassByCGLIB$$2bda5a73.invoke(<generated>)
    at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
    at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:698)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:150)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:110)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:631)
    at myModule.package.SomeClass$$EnhancerByCGLIB$$37044080.myMethod(<generated>)
    at myModule.package.SomeClass.someMethod(SomeClass.java:64)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at org.apache.cxf.service.invoker.AbstractInvoker.performInvocation(AbstractInvoker.java:173)
    at org.apache.cxf.service.invoker.AbstractInvoker.invoke(AbstractInvoker.java:89)
    ... 34 more

In particular, I would like to focus the attention on

myModule.package.SomeOtherClass.someOtherMethod(SomeClass.java:114)

and

myModule.package.SomeClass.someMethod(SomeClass.java:64)

Their codes look like

@Transactional("myModuleransactionManager")
public ... someOtherMethod(){
   ...
}

and

public ... someMethod(){
   ...
}

So, in my understanding this configuration should work, why does it throw that exception? Is a standard named transaction manager required? Or is it something due to cxf? I found some questions related to multiple transaction manager within the same application (example 1, example2) but the accepted answer in those questions drive to my solution. What did I misunderstand and I am doing wrong?
Thanks to everybody who is willing to read this long question till here!

EDIT to provide a complete explanation based on Michail's answer: using Spring Data JPA there is the need to define repositories interfaces to connect to the database. someOtherMethod is indeed calling one of my repositories which is defined as:

@Repository("myRepository")
@Transactional(propagation = Propagation.NESTED, value = "myModuleransactionManager")
public interface MyRepository extends JpaRepository<MyEntity, Integer>
{

}

This again looks fine, but looking at JpaRepository implementation source code (so, looking at org.springframework.data.jpa.repository.support.SimpleJpaRepository I discovered that the save (as well as other update methods) is annotated with @Transactional. Code from SimpleJpaRepository

    @Transactional
    public <S extends T> S save(S entity) {

        if (entityInformation.isNew(entity)) {
            em.persist(entity);
            return entity;
        } else {
            return em.merge(entity);
        }
    }

Therefore, when using Spring Data JPA the default transaction manager (the one named transactionManager) is mandatory. Bad for my goal, but at least I now know that's it!

Endorsed answered 28/2, 2013 at 19:9 Comment(0)
S
10

Looks like your someOtherMethod calls any other @Transactional component (some class with save method). And I think it has @Transactinal() (empty) annotation (which uses default bean named transactionManager).

You may see 2 TransactionInterceptor positions in stacktrace. Please provide some details about it.

Scevor answered 28/2, 2013 at 21:53 Comment(1)
Michail, your answer drove me into the right direction to find the root cause of my problem. My configuration is perfectly fine, the problem resides in Spring Data Jpa. I extended your answer to provide the explanation of this statement.Endorsed
G
6

I suspect that you just need to ensure that your repositories use the correctly named transaction manager in your @EnableJpaRepositories annotation.

i.e.

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
        entityManagerFactoryRef = "fooEntityManagerFactory", 
        transactionManagerRef = "fooTransactionManager",
        basePackages = {"com.sctrcd.multidsdemo.integration.repositories.foo"})
public class FooConfig {
    //...
}

It took me a while to figure out the details, so I have just provided a fuller explanation of how to configure Spring Data JPA repositories to work with multiple datasources here:

Multiple jpa:repositories in xml config, how to configure with @EnableJPARepositories using Spring java config?

And a full project demonstrating it here:

https://github.com/gratiartis/multids-demo

Giverin answered 14/11, 2013 at 11:15 Comment(1)
I ended up using a single transaction manager (named transactionManager so Spring JPA is happy), with multiple datasources. In code terms, I used lots of abstract classes to define basic behaviors and specific configuration classes which extend the abstract ones. That said, if I can now figure out how I did that I may create a sample project on github to share my implementation. May be useful.Endorsed
W
2

Actually, there is a way to use named TransactionManager with Spring Data JPA. This works for me:

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

<jpa:repositories base-package="com.xxx.yyy" entity-manager-factory-ref="myEntityManagerFactory" transaction-manager-ref="myTransactionManager">
</jpa:repositories>
Warchaw answered 25/1, 2014 at 8:36 Comment(1)
The crucial part which I was missing is the transaction-manager-ref="myTransactionManager" on the <jpa:repositories> tag. It's neither necessary not enough to specify the transaction manager at the <tx:annotation-driven> tag.Fachan
D
0

I found your question very interesting conceptully. And thus was able to revise some of my long forgotten concepts. Looks like its a limitation on java config side. So you will have to resort to a bit of xml in between and then giv transactionmanager something like

<tx:annotation-driven transaction-manager="myModuletransactionManager"/>

then you can give use your transactionManager. Default SimpleJpaRepository will also use the new one only.

Update: Or by the way you can use this through Config also now it seems EnableTransactionManagement

Disused answered 1/3, 2013 at 12:38 Comment(4)
I already have my Spring configuration class annotated with @EnableTransactionManagement,which doesn't help. Moreover, source code of SimpleJpaRepository clearly use @Transaction. Spring documentation states: You can omit the transaction-manager attribute in the <tx:annotation-driven/> tag if the bean name of the PlatformTransactionManager that you want to wire in has the name transactionManager. If the PlatformTransactionManager bean that you want to dependency-inject has any other name, then you have to use the transaction-manager attribute explicitly, as in the preceding example.Endorsed
Thus, when @Transaction is used without the value attribute I understand there is no way to use other than the standard named transactionManager. I now moved on and have some stuff to do, but as soon as I'll commit such stuff, I'll have a small idea I want to try! Probably already later today. Then, I will eventually update this thread.Endorsed
No, that is for overriding default transactionManager bean name only, I believeDisused
Yep, that <tx:annotation-driven /> is used to override the default name. On the other hand, however, @Transaction is equivalent to @Transaction("transactionManager"), that is why if I change the default name then SimpleJpaRepository stops working.Endorsed
J
0

I have qualified @Transactional at my service layer. And it seems to me that we can disable transaction management in Spring Data repository as following:

<jpa:repositories base-package="ru.xdsoft.conn.thanksapp.thanks.dao.repository"
                  entity-manager-factory-ref="thanksEntityManagerFactory"
                  enable-default-transactions="false"
/>
<jpa:repositories base-package="ru.xdsoft.conn.thanksapp.orgstruct.dao"
                  entity-manager-factory-ref="orgStructEntityManagerFactory"
                  enable-default-transactions="false"
/>

Not sure by 100% but error is gone. Found it here: https://github.com/spring-projects/spring-data-jpa/blob/master/src/test/resources/org/springframework/data/jpa/repository/support/disable-default-transactions.xml

Jobholder answered 22/9, 2016 at 19:9 Comment(0)
M
0

I've done it a couple of times, so here's how to do it completely in Java code (no xml). I do use Lombok however, which I highly recommend. I focus solely upon the asked problem, so if you've never done this before, read the Spring docs for configuring additional details concerning JPA dialects and spring datasource driver-classes.

Explanation: when calling the JPA embedded methods like findAll() or save() the TransactionInterceptor will look for the default "transactionManager" Here's what you need to connect multiple databases through Hibernate and JPA.

  1. Define your variables in the application.properties
first.datasource.url=my/database/url/example
first.datasource.username=username-example
first.datasource.password=password-example

second.datasource.url=my/database/url/example
second.datasource.username=username-example
second.datasource.password=password-example
  1. Create your databaseConfig
import org.apache.commons.dbcp2.BasicDataSource;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
        entityManagerFactoryRef = "firstEntityManagerFactory", basePackages = {
        "be.company.appname.repository.firstdatabase", "be.company.appname.config.firstdatabase"
})
public class FirstDatabaseConfig {

    @Value("${first.datasource.url}")
    private String url;
    @Value("${first.datasource.username}")
    private String username;
    @Value("${first.datasource.password}")
    private String password;
    @Value("${spring.datasource.driver-class-name}")
    private String driver;

    @Primary
    @Bean(name = "firstDataSourceProperties")
    @ConfigurationProperties("first.datasource")
    public DataSourceProperties dataSourceProperties() {
        return new DataSourceProperties();
    }

    @Primary
    @Bean(name = "firstDataSource")
    @ConfigurationProperties("first.datasource.configuration")
    public DataSource dataSource(@Qualifier("firstDataSourceProperties") DataSourceProperties dataSourceProperties) {
        BasicDataSource dataSource = new BasicDataSource();
        dataSource.setDriverClassName(driver);
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        dataSource.setRemoveAbandonedOnBorrow(true);
        dataSource.setRemoveAbandonedOnMaintenance(true);
        dataSource.setInitialSize(10);
        dataSource.setMaxTotal(20);
        dataSource.setValidationQuery("select 1 from MY_SCHEMA.TABLE");
        dataSource.setValidationQueryTimeout(900_000);
        dataSource.setTestWhileIdle(true);
        dataSource.setLogAbandoned(true);
        dataSource.setTestOnReturn(true);
        dataSource.setTestOnBorrow(true);
        dataSource.setDefaultAutoCommit(false);
        return dataSource;
    }

    @Primary
    @Bean(name = "firstEntityManagerFactory")
    public LocalContainerEntityManagerFactoryBean entityManagerFactory(
            EntityManagerFactoryBuilder builder,
            @Qualifier("firstDataSource") DataSource firstDataSource
    ) {
        return builder
                .dataSource(firstDataSource)
                .packages("be.company.appname.model.firstdatabase")
                .persistenceUnit("first")
                .build();
    }

    @Primary
    @Bean(name = "firstTransactionManager")
    public PlatformTransactionManager transactionManager(
            @Qualifier("firstEntityManagerFactory") EntityManagerFactory firstEntityManagerFactory
    ) {
        return new JpaTransactionManager(firstEntityManagerFactory);
    }
}

A few notes on this:

  • notice the @Primary! You'll only need this on 1 of the databaseConfigs! For your second database, you can use the same code and make the obvious changes, including the namechanges (e.g. firstEntityManagerFactory becomes secondEntityManagerFactory etc.), changing the appropriate variables, changing the ValidationQuery and defining the correct packages.
  • in the second databaseConfig, I removed "be.company.appname.config.firstdatabase" from the basePackages = {} declaration. Only the pointer to the repository package will do.
  1. Create your databaseObject in the package you defined within the firstEntityManagerFactory bean.
@Entity
@Table(name = "USER")
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MyUser {

    @Id
    @Column(name = "ID")
    private Long id;

    @Column(name = "USERNAME")
    private String userName;

    @Column(name = "UID")
    private String uid;

    @Column(name = "FIRST_NAME")
    private String firstName;

    @Column(name = "LAST_NAME")
    private String lastName;
  • @Table is the exact name of your databasetable
  • @Column is the exact name of your databasetable-column. Your own field-variables don't have to match, but I do it out of habit (e.g. declaring @Column(name = "USERNAME") private String name; would also work
  1. Create your repository in the package you declared in the databaseConfig class
@Repository
@Transactional(value = "firstTransactionManager")
public interface MyUserRepository extends JpaRepository<MyUser, Long> {

    List<MyUser> findAll();
}

What causes the asker's exception to appear? For example:

I call MyUserRepository.findById(1L) without the method being declared in my repository. It is a known shorthand of JPA default embedded. Take a look at the details for your own JPA shorthand queries. If not declared in the repository, your application will look for the default transactionManager bypassing your repository-interface. But by declaring the method, your application will know to look for your own custom firstTransactionManager

note: The creation of the BasicDataSource may vary upon which database you're using. I'm using the DB2Dialectfor connection to an AS400.

Marlysmarmaduke answered 4/6, 2021 at 13:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.