1
votes

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?

1

1 Answers

0
votes

You have to edit your postgresql.conf file, find max_prepared_transactions and uncomment It (remove the # at the beginning of the line) and set a reasonable value. See : https://www.postgresql.org/docs/9.4/static/runtime-config-resource.html