spring 启动应用程序超时后如何以与 weblogic 相同的方式回滚事务
How rollback transaction after timeout in spring boot application in same way as on weblogic
所以在我的 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 引导应用程序中进行相同的操作,所以我尝试了这个:
@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 因为现在我正在测试它应该在单线程运行中的一些任务
在 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.
XA transactions 非常复杂,您真的希望有一个很好的理由来使用它们(即添加一些业务流程来消除对 XA 的需求实际上是不可能的),因为您将得到在野外遇到麻烦...
也就是说,我的猜测是 XA 阶段之间的超时差异。
对于 XA,有 2 个超时 - 第一个阶段的超时,称为投票阶段(通常由 @Transactional 注释设置,但这取决于 JTA 提供程序)和另一个超时第二阶段,称为提交阶段,通常要长得多,因为事务管理器已经获得所有各方的同意,提交已准备就绪,因此为瞬态网络故障等问题提供了更大的回旋余地.
我的猜测是 WebLogic JTA 在处理参与者返回的第二阶段通知方面与 Atomikos 的行为完全不同,直到 atomikos 被更改为使用多线程 ack。
如果您的应用程序只有您和数据库,那么您可能不需要 XA 事务管理器。我希望这会按照您想要的超时方式运行。
祝你好运!