Mastering Spring Transaction Timeouts: Configurations, Tests, and Internals
This article explains how to configure transaction timeout in Spring 5.3.23 using annotations and programmatic approaches, demonstrates four test scenarios with a large table, analyzes the resulting timeout exceptions, and delves into the underlying mechanisms in DataSourceTransactionManager, JdbcTemplate, and related utility classes.
Environment: Spring 5.3.23
1. Configure Transaction Timeout
Annotation method:
// unit is seconds
@Transactional(timeout = 2)
public void save() {
}Programmatic method 1:
@Resource
private PlatformTransactionManager tm;
DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
definition.setTimeout(2);Programmatic method 2:
@Resource
private PlatformTransactionManager tm;
public void update() {
TransactionTemplate template = new TransactionTemplate(tm);
template.setTimeout(2);
template.execute(new TransactionCallback<Object>() {
@Override
public Object doInTransaction(TransactionStatus status) {
// ...
return null;
}
});
}2. Prepare Environment
Create a table with 20 million rows and no indexes except the primary key.
3. Simulate Transaction Timeout
Test 1
Count rows inside a transaction with a 20‑second timeout.
// Set timeout to 20s
@Transactional(timeout = 20)
public void query() {
long start = System.currentTimeMillis();
jdbcTemplate.execute("select count(*) from p_user");
System.out.println("耗时:" + (System.currentTimeMillis() - start) + "毫秒");
}Result: 3198 ms (within the timeout).
Changing the timeout to 3 seconds triggers a MySQLTimeoutException from the driver:
com.mysql.cj.jdbc.exceptions.MySQLTimeoutException: Statement cancelled due to timeout or client request
at com.mysql.cj.jdbc.StatementImpl.checkCancelTimeout(StatementImpl.java:2167)Test 2
Sleep 3 seconds before the DB operation, timeout set to 2 seconds.
@Transactional(timeout = 2)
public void query() {
long start = System.currentTimeMillis();
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
jdbcTemplate.execute("select 1");
System.out.println("耗时:" + (System.currentTimeMillis() - start) + "毫秒");
}Result: Spring throws a TransactionTimedOutException.
Test 3
Execute the DB operation first, then sleep 3 seconds (still timeout 2 seconds).
@Transactional(timeout = 2)
public void query() {
long start = System.currentTimeMillis();
jdbcTemplate.execute("select 1");
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("耗时:" + (System.currentTimeMillis() - start) + "毫秒");
}Result: The method finishes normally (≈3015 ms) because the timeout is checked only before each DB call.
Test 4
Same as Test 3 but perform a second DB call after the sleep.
@Transactional(timeout = 2)
public void query() {
long start = System.currentTimeMillis();
jdbcTemplate.execute("select 1");
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("耗时:" + (System.currentTimeMillis() - start) + "毫秒");
// second DB operation
jdbcTemplate.execute("select 1");
}Result: The first DB call succeeds; the second triggers a TransactionTimedOutException, confirming that the timeout is evaluated at each database interaction.
4. Transaction Timeout Mechanism
When a transaction starts, DataSourceTransactionManager#doBegin sets the timeout:
protected void doBegin(Object transaction, TransactionDefinition definition) {
int timeout = determineTimeout(definition);
if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
}
}
protected int determineTimeout(TransactionDefinition definition) {
if (definition.getTimeout() != TransactionDefinition.TIMEOUT_DEFAULT) {
return definition.getTimeout();
}
return getDefaultTimeout();
}When JdbcTemplate creates a Statement, it applies the timeout:
protected void applyStatementSettings(Statement stmt) throws SQLException {
DataSourceUtils.applyTimeout(stmt, getDataSource(), getQueryTimeout());
}DataSourceUtils.applyTimeout chooses the remaining transaction timeout or the explicit value:
public static void applyTimeout(Statement stmt, @Nullable DataSource dataSource, int timeout) throws SQLException {
ConnectionHolder holder = null;
if (dataSource != null) {
holder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
}
if (holder != null && holder.hasTimeout()) {
stmt.setQueryTimeout(holder.getTimeToLiveInSeconds());
} else if (timeout >= 0) {
stmt.setQueryTimeout(timeout);
}
}ResourceHolderSupport computes the remaining time and throws TransactionTimedOutException when the deadline is reached:
public int getTimeToLiveInSeconds() {
double diff = ((double) getTimeToLiveInMillis()) / 1000;
int secs = (int) Math.ceil(diff);
checkTransactionTimeout(secs <= 0);
return secs;
}
private void checkTransactionTimeout(boolean deadlineReached) throws TransactionTimedOutException {
if (deadlineReached) {
setRollbackOnly();
throw new TransactionTimedOutException("Transaction timed out: deadline was " + this.deadline);
}
}In summary, the transaction start records the deadline; each database operation checks the elapsed time against this deadline, and if exceeded, Spring rolls back the transaction and throws a timeout exception.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Spring Full-Stack Practical Cases
Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
