I have a Spring MVC project with Hibernate (Spring 4.2.2 - Hibernate 4.3.6). I wanted to connect two datasources (two different postgresql databases) to it, so I used atomikos as implementation for JTA. I have my configuration all by annotations, so the Configuration file is this:
@Configuration
@PropertySource(value = { "classpath:hibernate.properties" })
public class HibernateConfig {
@Autowired
private Environment environment;
// First DB connection
@Primary
@Bean(name = "sessionFactory")
@DependsOn("setMyAtomikosSystemProps")
public LocalSessionFactoryBean sessionFactory() {
LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
sessionFactory.setDataSource(dataSource());
sessionFactory.setPackagesToScan(new String[] { "org.spring.model" });
sessionFactory.setHibernateProperties(hibernateProperties());
return sessionFactory;
}
@Bean(name = "statsSessionFactory")
@DependsOn("setMyAtomikosSystemProps")
public LocalSessionFactoryBean statsSessionFactory() {
LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
sessionFactory.setDataSource(statsDataSource());
sessionFactory.setPackagesToScan(new String[] { "org.spring.stats.model" });
sessionFactory.setHibernateProperties(hibernateProperties());
return sessionFactory;
}
@Primary
@Bean(name = "dataSource")
public DataSource dataSource() {
AtomikosDataSourceBean ds = new AtomikosDataSourceBean();
ds.setUniqueResourceName("first");
ds.setXaDataSourceClassName("org.postgresql.xa.PGXADataSource");
ds.setXaProperties(dataSourceProperties("first"));
ds.setMinPoolSize(5);
ds.setMaxPoolSize(75);
ds.setMaxIdleTime(60 * 15);
return ds;
}
@Bean(name = "statsDataSource")
public DataSource statsDataSource() {
AtomikosDataSourceBean ds = new AtomikosDataSourceBean();
ds.setUniqueResourceName("second");
ds.setXaDataSourceClassName("org.postgresql.xa.PGXADataSource");
ds.setXaProperties(dataSourceProperties("second"));
ds.setMinPoolSize(5);
ds.setMaxPoolSize(75);
ds.setMaxIdleTime(60 * 15);
return ds;
}
private Properties dataSourceProperties(String database) {
Properties p = new Properties();
p.setProperty("user", environment.getRequiredProperty("hibernate.connection.username"));
p.setProperty("password", environment.getRequiredProperty("hibernate.connection.password"));
p.setProperty("serverName", environment.getRequiredProperty("hibernate.connection.url"))
p.setProperty("portNumber", environment.getRequiredProperty("hibernate.connection.port"))
p.setProperty("databaseName", database);
return p;
}
@Bean(name = "userTransactionService")
@DependsOn("setMyAtomikosSystemProps")
public UserTransactionService userTransactionService() {
UserTransactionServiceImp uts = new UserTransactionServiceImp();
Properties prop = new Properties();
prop.setProperty("com.atomikos.icatch.service", "com.atomikos.icatch.standalone.UserTransactionServiceFactory");
uts.init(prop);
return uts;
}
@Bean
@DependsOn("userTransactionService")
public UserTransactionManager AtomikosTransactionManager() {
UserTransactionManager utm = new UserTransactionManager();
utm.setForceShutdown(true);
utm.setStartupTransactionService(false);
return utm;
}
@Bean
@DependsOn("userTransactionService")
public UserTransaction AtomikosUserTransaction() {
UserTransactionImp ut = new UserTransactionImp();
try {
ut.setTransactionTimeout(300);
} catch (SystemException e) {
e.printStackTrace();
}
return ut;
}
@Bean
@DependsOn("userTransactionService")
public PlatformTransactionManager JtaTransactionManager() {
JtaTransactionManager jtaTM = new JtaTransactionManager();
jtaTM.setTransactionManager(AtomikosTransactionManager());
jtaTM.setUserTransaction(AtomikosUserTransaction());
jtaTM.setAllowCustomIsolationLevels(true);
return jtaTM;
}
// SharedProperties
private Properties hibernateProperties() {
Properties properties = new Properties();
properties.put("hibernate.dialect", environment.getRequiredProperty("hibernate.dialect"));
properties.put("hibernate.show_sql", environment.getRequiredProperty("hibernate.show_sql"));
properties.put("hibernate.format_sql", environment.getRequiredProperty("hibernate.format_sql"));
// JTA
properties.put("hibernate.current_session_context_class", "jta");
properties.put("hibernate.transaction.factory_class", "org.hibernate.transaction.JTATransactionFactory");
properties.put("hibernate.transaction.jta.platform", "com.atomikos.icatch.jta.hibernate4.AtomikosPlatform");
return properties;
}
@Bean
public MethodInvokingFactoryBean setMyAtomikosSystemProps() {
MethodInvokingFactoryBean mifb = new MethodInvokingFactoryBean();
Properties p = new Properties();
p.setProperty("com.atomikos.icatch.hide_init_file_path", "true");
p.setProperty("com.atomikos.icatch.no_file", "true");
mifb.setArguments(new Object[] { p });
mifb.setTargetObject(java.lang.System.getProperties());
mifb.setTargetMethod("putAll");
return mifb;
}
}
This configuration works if the transaction involves only one datasource, but if I create a Service that wants get data from both, I receive this error:
GRAVE: Servlet.service() for servlet [dispatcher] in context with path [/api] threw exception [Request processing failed; nested exception is org.springframework.transaction.UnexpectedRollbackException: JTA transaction unexpectedly rolled back (maybe due to a timeout); nested exception is javax.transaction.RollbackException: Prepare: NO vote] with root cause
com.atomikos.icatch.RollbackException: Prepare: NO vote
at com.atomikos.icatch.imp.ActiveStateHandler.prepare(ActiveStateHandler.java:202)
at com.atomikos.icatch.imp.CoordinatorImp.prepare(CoordinatorImp.java:523)
at com.atomikos.icatch.imp.CoordinatorImp.terminate(CoordinatorImp.java:687)
at com.atomikos.icatch.imp.CompositeTransactionImp.commit(CompositeTransactionImp.java:282)
at com.atomikos.icatch.jta.TransactionImp.commit(TransactionImp.java:172)
at com.atomikos.icatch.jta.TransactionManagerImp.commit(TransactionManagerImp.java:414)
at com.atomikos.icatch.jta.UserTransactionImp.commit(UserTransactionImp.java:86)
at org.springframework.transaction.jta.JtaTransactionManager.doCommit(JtaTransactionManager.java:1021)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:761)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:730)
at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:485)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:291)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:207)
at com.sun.proxy.$Proxy91.findById(Unknown Source)
But this is NOT a timeout problem because the response come in few seconds.
I receive also a very long list of errors from postgres. I'm "filtering" the errors:
WARN XAResourceTransaction - XA resource 'second': rollback for XID '3139322E3136382E322E322E746D313438373639323737373736333030303032:3139322E3136382E322E322E746D32' raised -4: the supplied XID is invalid for this XA resource
org.postgresql.xa.PGXAException: Errore durante il «rollback» di una transazione preparata
(this one is in italian, it says "error during the "rollback" of a prepared transaction)
Caused by: org.postgresql.util.PSQLException: ERROR: prepared transactions are disabled
Suggerimento: Set max_prepared_transactions to a nonzero value.
Caused by: org.postgresql.util.PSQLException: ERROR: prepared transactions are disabled
Suggerimento: Set max_prepared_transactions to a nonzero value.
ERROR XAResourceTransaction - XA resource 'first': prepare for XID '3139322E3136382E322E322E746D313438373639323737373736333030303032:3139322E3136382E322E322E746D33' raised -3: the XA resource detected an internal error
org.postgresql.xa.PGXAException: Error in preparing transaction
So it seems it needs prepared transaction, but because only for transaction that include both of them? And it is mandatory enable them? Can I avoid this?