Edited at

Spring Bootとmybatisでトランザクションタイムアウトが効かない

More than 3 years have passed since last update.


トランザクションコミット前のチェック


  • DataSourceTransactionManagerは、トランザクションのコミット前にトランザクションタイムアウトのチェックをしない。

  • JtaTransactionManagerは、トランザクションのコミット前にチェックを実施し、指定した時間が経過していた場合には、TransactionTimedOutExceptionが発生する。(実装はAtomikosで確認)


SQL実行前のチェック


  • JdbcTemplateは、SQL実行前にトランザクションタイムアウトチェックを実施し、指定した時間が経過していた場合にはTransactionTimedOutExceptionが発生する。また、トランザクションタイムアウトまでの残り時間をStatement#setQueryTimeoutに指定する。

  • mybatisは、トランザクションタイムアウトのチェックは実施しない。

上記をまとめると以下のようになる。

TxManager
SQL
コミット前チェック
SQL実行前チェック

JtaTransactionManager
JdbcTemplate

JtaTransactionManager
mybatis

×

DataSourceTransactionManager
JdbcTemplate
×

DataSourceTransactionManager
mybatis
×
×

つまりDataSourceTransactionManagerとmybatisの組み合わせでは、トランザクションタイムアウトの設定が無視されることになる。


JTAを利用せずにタイムアウトチェックを実施するには

以下のようにDataSourceTransactionManager#doCommitを拡張すれば、コミット前に必ずトランザクションのチェックが実行される。

@Configuration

public class TransactionConfiguration {

@Bean
@Autowired
public DataSourceTransactionManager transactionManager(
DataSource dataSource) {
DataSourceTransactionManager transactionManager =
new TimeoutAwareTransactionManager();
transactionManager.setDefaultTimeout(10000); //タイムアウト値設定
transactionManager.setDataSource(dataSource);
transactionManager.setRollbackOnCommitFailure(true);
return transactionManager;
}

public static class TimeoutAwareTransactionManager
extends DataSourceTransactionManager {

private static final long serialVersionUID = 1L;

@Override
protected void doCommit(DefaultTransactionStatus status) {
JdbcTransactionObjectSupport txObject =
(JdbcTransactionObjectSupport) status.getTransaction();

ConnectionHolder holder = txObject.getConnectionHolder();

if (holder.hasTimeout()) {
// トランザクションタイムアウトしていたらTransactionTimedOutExceptionがスローされる。
holder.getTimeToLiveInMillis();
}
super.doCommit(status);
}

}

}


暗黙コミットについて

余談だがAbstractPlatformTransactionManager#rollbackOnCommitFailureはdefaultがfalseになっているため、

明示的にtrueにしないと、JDBC Driverの実装によっては、Connection#rollbackを実行しない限り暗黙コミットが走ってしまう。

(少なくともOracle JDBC Driverを利用した場合はConnection#setAutoCommit(false)が実行されているとclose時に暗黙コミットされる)

また、Connectionプールを用いても、DataSourceTransactionManager#doCleanupAfterCompletionがConnection#setAutoCommit(true)を実行しておりこのタイミングで暗黙コミットがかかる。


任意のタイミングでチェックする

任意の@Repository@ServiceなどBeanの呼び出しのタイミングでチェックを実施するには以下のようなAdviceを作って適用してあげればよい。

@Aspect

@Component
@Slf4j
public class TransactionTimeoutAdvice {

@Autowired
private DataSource dataSource;

@Before("適用先のpointcut")
public void checkTimeout() {
ConnectionHolder holder =
(ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
// タイムアウトしているとTransactionTimedOutExceptionが発生
if (holder != null && holder.hasTimeout()) {
long ttl = holder.getTimeToLiveInMillis();
log.debug("transaction will be timed out after " + ttl + " msec.");
}
}

}