在一个全局事务的范围内,使用JTA同时向不同的数据源调用几个查询

Vla*_*uer 8 java spring multithreading jta atomikos

我有一个带有3个分布式dataSources的应用程序(com.atomikos.jdbc.AtomikosDataSourceBean).我正在使用Atomikos事务管理器作为JTA实现.每个dataSource都适用于PostgreSQL数据库.现在,我正在调用我对每个dataSource的查询,一切正常.

我想知道,如果有可能,使用JTA,并行调用我的查询(多线程,并发)?

我试过简单地使用jdbcTemplate(Spring)在新创建的线程中调用查询.首先,我遇到了一个春天问题.Spring将事务上下文存储在ThreadLocal字段中,因此在我的新线程中无法正确解析(Spring事务管理器和多线程)).我已经通过将相同的事务上下文设置为新创建的线程的ThreadLocal来解决了这个问题.但是我在Atomikos代码中面临同样的问题.它们还将CompositeTransactionImp存储在线程范围映射(BaseTrancationManager#getCurrentTx)中.但在Atomikos案例中,不可能为新线程设置值.所以我不能同时执行我的查询,因为似乎Atomikos不支持这种方法.但我也查看了JTA规范并发现了以下内容:"多个线程可能同时与同一个全局事务关联." ("3.2 TransactionManager接口",http://download.oracle.com/otndocs/jcp/jta-1.1-spec-oth-JSpec/?submit=Download)

问题:如何在一个全局事务的范围内使用JTA(2阶段提交)同时调用两个或多个查询到不同的dataSource?

在tomcat上下文中配置DataSources:

<Resource name="jdbc/db1" auth="Container" type="com.atomikos.jdbc.AtomikosDataSourceBean"
          factory="com.company.package.AtomikosDataSourceBeanFactory"
          xaDataSourceClassName="org.postgresql.xa.PGXADataSource"
          xaProperties.serverName="localhost"
          xaProperties.portNumber="5451"
          xaProperties.databaseName="db1"
          uniqueResourceName="jdbc/db1"
          xaProperties.user="secretpassword"
          xaProperties.password="secretpassword"
          minPoolSize="5"
          maxPoolSize="10"
          testQuery="SELECT 1"  />

<Resource name="jdbc/db2" auth="Container" type="com.atomikos.jdbc.AtomikosDataSourceBean"
          factory="com.company.package.AtomikosDataSourceBeanFactory"
          xaDataSourceClassName="org.postgresql.xa.PGXADataSource"
          xaProperties.serverName="localhost"
          xaProperties.portNumber="5451"
          xaProperties.databaseName="db2"
          uniqueResourceName="jdbc/db2"
          xaProperties.user="secretpassword"
          xaProperties.password="secretpassword"
          minPoolSize="5"
          maxPoolSize="10"
          testQuery="SELECT 1"  />

<Resource name="jdbc/db3" auth="Container" type="com.atomikos.jdbc.AtomikosDataSourceBean"
          factory="com.company.package.AtomikosDataSourceBeanFactory"
          xaDataSourceClassName="org.postgresql.xa.PGXADataSource"
          xaProperties.serverName="localhost"
          xaProperties.portNumber="5451"
          xaProperties.databaseName="db3"
          uniqueResourceName="jdbc/db3"
          xaProperties.user="secretpassword"
          xaProperties.password="secretpassword"
          minPoolSize="5"
          maxPoolSize="10"
          testQuery="SELECT 1"  />
Run Code Online (Sandbox Code Playgroud)

Spring环境中的事务管理器配置:

 <bean id="transactionManager" class="com.atomikos.icatch.jta.UserTransactionManager"
  init-method="init" destroy-method="close" lazy-init="true">
  <property name="forceShutdown" value="false" />
 </bean>
Run Code Online (Sandbox Code Playgroud)

码:

    final SqlParameterSource parameters = getSqlParameterSourceCreator().convert(entity);

    // Solving Spring's ThreadLocal issue: saving thread local params
    final Map<Object, Object> resourceMap = TransactionSynchronizationManager.getResourceMap();
    final List<TransactionSynchronization> synchronizations = TransactionSynchronizationManager.getSynchronizations();
    final boolean actualTransactionActive = TransactionSynchronizationManager.isActualTransactionActive();
    final String currentTransactionName = TransactionSynchronizationManager.getCurrentTransactionName();
    final AtomicReference<Throwable> exceptionHolder = new AtomicReference<Throwable>();

    // Running query in a separate thread.
    final Thread thread = new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                // Solving Spring's ThreadLocal issue: setting thread local values to newly created thread.
                for (Map.Entry<Object, Object> entry : resourceMap.entrySet()) {
                    TransactionSynchronizationManager.bindResource(entry.getKey(), entry.getValue());
                }
                if (synchronizations != null && !synchronizations.isEmpty()) {
                    TransactionSynchronizationManager.initSynchronization();
                    for (TransactionSynchronization synchronization : synchronizations) {
                        TransactionSynchronizationManager.registerSynchronization(synchronization);
                    }
                }
                TransactionSynchronizationManager.setActualTransactionActive(actualTransactionActive);
                TransactionSynchronizationManager.setCurrentTransactionName(currentTransactionName);

                // Executing query.
                final String query = "insert into ...";
                NamedParameterJdbcTemplate template = new NamedParameterJdbcTemplate(dataSourceOne);

                template.update(query, parameters);
            } catch (final Throwable ex) {
                exceptionHolder.set(ex);
            }
        }
    });
    thread.start();

    // ... same code as above for other dataSources.

    // allThreds.join(); - joining to all threads.
Run Code Online (Sandbox Code Playgroud)

ben*_*n75 1

我认为您解决 TransactionSynchronizationManager 必须使用单线程事务这一事实的想法很有趣,但可能很危险。

在 TransactionSynchronizationManager 中,事务资源存储在 ThreadLocal Map 中,其中关键是资源工厂,我想知道当您使用同一资源工厂通过多个线程执行此解决方法时会附加什么 - 它可能不适用于您的案例,因为你有 3 个数据源 -。(乍一看,我会说你的一个事务资源将被另一个事务资源取代,但也许我错过了一些东西......)。

不管怎样,我认为你可以尝试使用javax.transaction.TransactionManager.resume()来实现你想要做的事情。

这个想法是直接使用 JTA api,从而绕过单线程 Spring 事务支持。

这是一些代码来说明我的想法:

@Autowired
JtaTransactionManager txManager;  //from Spring

javax.transaction.TransactionManager jtaTransactionManager;

public void parallelInserts() {
    jtaTransactionManager = txManager.getTransactionManager();  //we are getting the underlying implementation
    jtaTransactionManager.begin();
    final Transaction jtaTransaction  = jtaTransactionManager.getTransaction();
    try {
      Thread t1 = new Thread(){
        @Override
        public void run() {
            try {
                jtaTransactionManager.resume(jtaTransaction);
                //... do the insert
            } catch (InvalidTransactionException e) {
                try {
                    jtaTransaction.setRollbackOnly();
                } catch (SystemException e1) {
                    e1.printStackTrace();
                }
                e.printStackTrace();
            } catch (SystemException e) {
                e.printStackTrace();
            }
        }
      };
      t1.start();
      //same with t2 and t3
    } catch (Exception ex) {
        jtaTransactionManager.setRollbackOnly();
        throw ex;
    }
    //join threads and commit
    jtaTransactionManager.commit();
}
Run Code Online (Sandbox Code Playgroud)

我认为这个解决方案可能有效(我必须说我自己没有尝试过)。我现在看到的唯一限制是您无法重用线程,因为没有与resume()调用相对应的部分,并且第二次调用resume()时您可能会遇到IllegalStateException。