Reputation: 7251
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!
Upvotes: 13
Views: 20351
Reputation: 1170
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.
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
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:
@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."be.company.appname.config.firstdatabase"
from the basePackages = {}
declaration. Only the pointer to the repository package will do.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@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 DB2Dialect
for connection to an AS400.
Upvotes: 0
Reputation: 1
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
Upvotes: 0
Reputation: 101
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>
Upvotes: 2
Reputation: 9490
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:
And a full project demonstrating it here:
https://github.com/gratiartis/multids-demo
Upvotes: 6
Reputation: 902
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
Upvotes: 0
Reputation: 3775
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.
Upvotes: 10