Spring MVC несколько источников данных JTA атомикос в одной транзакции

У меня есть проект Spring MVC с Hibernate (Spring 4.2.2 - Hibernate 4.3.6). Я хотел подключить к нему два источника данных (две разные базы данных postgresql), поэтому я использовал atomikos в качестве реализации для JTA. У меня есть моя конфигурация с аннотациями, поэтому файл конфигурации выглядит так:

@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;
    }
}

Эта конфигурация работает, если транзакция включает в себя только один источник данных, но если я создаю Службу, которая хочет получить данные от обоих, я получаю эту ошибку:

    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)

Но это не проблема тайм-аута, потому что ответ приходит через несколько секунд.

Я также получаю очень длинный список ошибок от postgres. Я "фильтрую" ошибки:

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 

(это на итальянском, там написано "ошибка во время" отката "подготовленной транзакции)

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

Таким образом, кажется, что требуется подготовленная транзакция, но потому что только для транзакции, которая включает их обоих? И обязательно ли их включать? Могу ли я избежать этого?

1 ответ

Вы должны отредактировать свой файл postgresql.conf, найти max_prepared_transactions и раскомментировать его (удалить знак # в начале строки) и установить разумное значение. См.: https://www.postgresql.org/docs/9.4/static/runtime-config-resource.html

Другие вопросы по тегам