在Spring Boot应用程序中如何像Weblogic一样在超时后回滚事务?

11

在我的Weblogic应用程序中,我们使用了一些jtaWeblogicTransactionManager。它有一些默认超时时间,可以在注释@Transactional(timeout = 60)中被覆盖。我创建了一个无限循环从数据库读取数据,并正确地超时:

29 Apr 2018 20:44:55,458 WARN  [[ACTIVE] ExecuteThread: '9' for queue: 'weblogic.kernel.Default (self-tuning)'] org.springframework.jdbc.support.SQLErrorCodesFactory : Error while extracting database name - falli
ng back to empty error codes
org.springframework.jdbc.support.MetaDataAccessException: Error while extracting DatabaseMetaData; nested exception is java.sql.SQLException: Unexpected exception while enlisting XAConnection java.sql.SQLExceptio
n: Transaction rolled back: Transaction timed out after 240 seconds 
BEA1-2C705D7476A3E21D0AB1
        at weblogic.jdbc.jta.DataSource.enlist(DataSource.java:1760)
        at weblogic.jdbc.jta.DataSource.refreshXAConnAndEnlist(DataSource.java:1645)
        at weblogic.jdbc.wrapper.JTAConnection.getXAConn(JTAConnection.java:232)
        at weblogic.jdbc.wrapper.JTAConnection.checkConnection(JTAConnection.java:94)
        at weblogic.jdbc.wrapper.JTAConnection.checkConnection(JTAConnection.java:77)
        at weblogic.jdbc.wrapper.Connection.preInvocationHandler(Connection.java:107)
        at weblogic.jdbc.wrapper.Connection.getMetaData(Connection.java:560)
        at org.springframework.jdbc.support.JdbcUtils.extractDatabaseMetaData(JdbcUtils.java:331)
        at org.springframework.jdbc.support.JdbcUtils.extractDatabaseMetaData(JdbcUtils.java:366)
        at org.springframework.jdbc.support.SQLErrorCodesFactory.getErrorCodes(SQLErrorCodesFactory.java:212)
        at org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator.setDataSource(SQLErrorCodeSQLExceptionTranslator.java:134)
        at org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator.<init>(SQLErrorCodeSQLExceptionTranslator.java:97)
        at org.springframework.jdbc.support.JdbcAccessor.getExceptionTranslator(JdbcAccessor.java:99)
        at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:655)
        at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:690)

现在我想在我的Spring Boot应用程序中实现相同的行为,所以我尝试了这个:

@EnableTransactionManagement
.
.
.

@Bean(name = "ds1")
@ConfigurationProperties(prefix = "datasource.ds1")
public DataSource logDataSource() {
    AtomikosDataSourceBean ds = new AtomikosDataSourceBean();
    return ds;
}

@Bean(name = "ds2")
@ConfigurationProperties(prefix = "datasource.ds2")
public DataSource refDataSource() {
    AtomikosDataSourceBean ds = new AtomikosDataSourceBean();
    return ds;
}
tm
@Bean(name = "userTransaction")
public UserTransaction userTransaction() throws Throwable {
    UserTransactionImp userTransactionImp = new UserTransactionImp();
    userTransactionImp.setTransactionTimeout(120);
    return userTransactionImp;
}

@Bean(name = "atomikosTransactionManager", initMethod = "init", destroyMethod = "close")
public TransactionManager atomikosTransactionManager() throws Throwable {
    UserTransactionManager userTransactionManager = new UserTransactionManager();
    userTransactionManager.setForceShutdown(false);
    userTransactionManager.setTransactionTimeout(120);
    return userTransactionManager;
}

@Bean(name = "transactionManager")
@DependsOn({ "userTransaction", "atomikosTransactionManager" })
public JtaTransactionManager transactionManager() throws Throwable {
    UserTransaction userTransaction = userTransaction();
    TransactionManager atomikosTransactionManager = atomikosTransactionManager();
    return new JtaTransactionManager(userTransaction, atomikosTransactionManager);
}

和application.properties:

datasource.ref.xa-data-source-class-name=oracle.jdbc.xa.client.OracleXADataSource
datasource.ref.unique-resource-name=ref
datasource.ref.xa-properties.URL=jdbc:oracle:thin:@...
datasource.ref.xa-properties.user=...
#datasource.ref.xa-properties.databaseName=...
datasource.ref.password=301d24ae7d0d69614734a499df85f1e2
datasource.ref.test-query=SELECT 1 FROM DUAL
datasource.ref.max-pool-size=5

datasource.log.xa-data-source-class-name=oracle.jdbc.xa.client.OracleXADataSource
datasource.log.unique-resource-name=log
datasource.log.xa-properties.URL=jdbc:oracle:thin:@...
datasource.log.xa-properties.user=...
#datasource.log.xa-properties.databaseName=...
datasource.log.password=e58605c2a0b840b7c6d5b20b3692c5db
datasource.log.test-query=SELECT 1 FROM DUAL
datasource.log.max-pool-size=5

spring.jta.atomikos.properties.log-base-dir=target/transaction-logs/
spring.jta.enabled=true
spring.jta.atomikos.properties.service=com.atomikos.icatch.standalone.UserTransactionServiceFactory
spring.jta.atomikos.properties.max-timeout=600000
spring.jta.atomikos.properties.default-jta-timeout=10000
spring.transaction.default-timeout=900

但是并没有成功。我的无限循环永远不会结束(我等待约15分钟然后停止我的应用程序)。唯一一次看到回滚的时候是当我尝试使用Thread.sleep,睡眠后此事务超时并回滚,但这不是我想要的结果。那么,是否有一种方法可以在超时后中断进程(在注释中使用超时或使用默认值),就像在我的WebLogic应用程序中一样?

更新

我是这样测试的:

public class MyService {

public void customMethod(){

customDao.readSomething();

}

}

public class CustomDao {

@Transactional(timeout = 120)
public void readSomething()

while(true){
 //read data from db. app on weblogic throw timeout, spring boot app in docker did  nothing and after 15 I give it up and kill it
}
}

}

更新2

当我开启atomikos调试时,我可以看到在初始化过程中有警告和一些atomikos定时器:

2018-05-03 14:00:54.833 [main] WARN  c.a.r.xa.XaResourceRecoveryManager - Error while retrieving xids from resource - will retry later...
javax.transaction.xa.XAException: null
    at oracle.jdbc.xa.OracleXAResource.recover(OracleXAResource.java:730)
    at com.atomikos.datasource.xa.RecoveryScan.recoverXids(RecoveryScan.java:32)
    at com.atomikos.recovery.xa.XaResourceRecoveryManager.retrievePreparedXidsFromXaResource(XaResourceRecoveryManager.java:158)
    at com.atomikos.recovery.xa.XaResourceRecoveryManager.recover(XaResourceRecoveryManager.java:67)
    at com.atomikos.datasource.xa.XATransactionalResource.recover(XATransactionalResource.java:449)
    at com.atomikos.datasource.xa.XATransactionalResource.setRecoveryService(XATransactionalResource.java:416)
    at com.atomikos.icatch.config.Configuration.notifyAfterInit(Configuration.java:466)
    at com.atomikos.icatch.config.Configuration.init(Configuration.java:450)
    at com.atomikos.icatch.config.UserTransactionServiceImp.initialize(UserTransactionServiceImp.java:105)
    at com.atomikos.icatch.config.UserTransactionServiceImp.init(UserTransactionServiceImp.java:219)
    at com.atomikos.icatch.jta.UserTransactionImp.checkSetup(UserTransactionImp.java:59)
    at com.atomikos.icatch.jta.UserTransactionImp.setTransactionTimeout(UserTransactionImp.java:127)

也许这就是原因。我该如何解决?我正在使用带有ojdbc8驱动程序的Oracle 12。

更新3

在修复UPDATE2以授予用户对数据库的权限后,我可以在日志中看到警告:

2018-05-03 15:16:30.207 [Atomikos:4] WARN  c.a.icatch.imp.ActiveStateHandler - Transaction 127.0.1.1.tm152535336001600001 has timed out and will rollback.

问题在于应用程序在超时后仍然从数据库中读取数据。为什么没有回滚?

更新4

所以我在ActiveStateHandler中发现了一个代码,当超时发生时:

...

        setState ( TxState.ACTIVE );
...

AtomikosConnectionProxy 检查超时的方式如下:

if ( ct.getState().equals(TxState.ACTIVE) ) ct.registerSynchronization(new JdbcRequeueSynchronization( this , ct ));
else AtomikosSQLException.throwAtomikosSQLException("The transaction has timed out - try increasing the timeout if needed");

那么为什么在 AtomikosConnectionProxy 中设置超时时间不会导致异常状态呢?

更新5

我发现了这个属性。

com.atomikos.icatch.threaded_2pc

将解决我的问题,现在它开始回滚,就像我想要的那样。但我仍然不明白为什么我应该将此设置为 true,因为现在我正在测试一些应该在单个线程中运行的任务。


也许这个链接可以帮到你:https://dev59.com/15Lea4cB1Zd3GeqP2nm_。 - alain.janinm
嗯,我可以尝试将参数oracle.jdbc.ReadTimeout=60000作为默认值,但当我想要在注释中更改某些较长事务的超时时间时,这仍然无法解决我的问题。 - hudi
ReadTimeout是针对单个语句的,它基本上是一个套接字超时,并且应该始终指定,否则如果存在网络问题,您将遇到问题。您在哪里循环?您在数据库中执行循环/睡眠的单个语句还是执行多个语句,每个语句都在合理的时间内完成? - ewramner
我在一些 @Transactional 方法中进行了测试,其中我有一个从数据库读取数据的无限循环。Weblogic 在超时后抛出异常,但Spring Boot应用程序在15分钟内没有做任何事情。 - hudi
你尝试过使用 javax.transaction.Transactional 吗?根据 (Spring 文档)[https://docs.spring.io/spring/docs/current/spring-framework-reference/data-access.html#transaction-declarative-annotations],它应该可以工作。虽然我对最新的 Spring 不是非常熟悉,但以前它对我来说是有效的,如果它解决了问题,我会将其发布为答案。 :) - Hash
显示剩余2条评论
2个回答

3
jta.properties中设置com.atomikos.icatch.threaded_2pc=true解决了我的问题。不知道为什么这个默认值在Web应用程序中被更改为false。
 * @param single_threaded_2pc (!com.atomikos.icatch.threaded_2pc)
 *           If true then commit is done in the same thread as the one that
 *            started the tx.

1

XA transactions非常复杂,你需要有非常充分的理由来使用它们(即添加某些业务流程无法避免使用XA),因为在实际操作中可能会遇到麻烦...

话虽如此,我猜测问题出在XA阶段之间超时时间不一致上。

使用XA事务有两个超时时间——第一个阶段的超时时间称为投票阶段(通常由@Transactional注释设置,但这取决于JTA提供程序),第二个阶段的超时时间称为提交阶段,通常要长得多,因为事务管理器已经获得了所有参与方的同意,可以进行提交,并且为诸如瞬时网络故障等提供更大的余地。

我猜想WebLogic JTA在处理来自参与方的第二阶段通知时与Atomikos的行为不同,直到Atomikos改用多线程确认为止。

如果您的应用程序只涉及您和数据库,则可能没有必要使用XA事务管理器。我预计这将符合您对超时的期望。

祝你好运!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接