Annotation Interface Transactional
When a method annotated with @Transactional is executed, it will automatically be wrapped in a database transaction. The transaction will be committed if the method completes normally, or rolled back if an exception is thrown.
Usage examples:
// Basic usage - starts a new transaction or joins existing one
@Transactional
public void saveUser(User user) {
userRepository.save(user);
auditRepository.log("User saved: " + user.getName());
}
// Always create a new transaction
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void logAudit(String message) {
auditRepository.log(message);
}
// Read-only transaction (may allow optimizations)
@Transactional(readOnly = true)
public List<User> findAllUsers() {
return userRepository.findAll();
}
// Custom rollback rules
@Transactional(rollbackFor = {BusinessException.class})
public void processOrder(Order order) throws BusinessException {
// ...
}
Important:
- Self-invocation (calling a
@Transactionalmethod onthisfrom within the same class) is intercepted: the framework's generated proxy is a subclass of the bean itself, not a delegate wrapping a separate target, so a call tothis.method()dispatches virtually onto the proxy's override. Re-verified against three pre-existingshouldInterceptSelfInvocationtest classes (2026-08-27) - Private, static, and final methods cannot be transactional - each is dispatched in a way
that bypasses the proxy (respectively:
invokespecial,invokestatic, and no override is possible); a package-private method is also ineligible when it is declared in a different package than the bean class - The class must not be final (subclass proxy limitation)
- Since:
- 6.2.0
- Author:
- wisdomme
- See Also:
-
Optional Element Summary
Optional ElementsModifier and TypeOptional ElementDescriptionThe transaction isolation level.Exception types that should NOT trigger a rollback, overriding theRuntimeException/Errordefault for those types.The transaction propagation behavior.booleanWhether the transaction is read-only.Exception types that should trigger a rollback, in addition to theRuntimeException/Errordefault.intThe transaction timeout in seconds.
-
Element Details
-
propagation
Propagation propagationThe transaction propagation behavior.Defaults to
Propagation.REQUIRED, which joins an existing transaction or creates a new one if none exists.- Returns:
- the propagation behavior
- Default:
REQUIRED
-
isolation
Isolation isolationThe transaction isolation level.Defaults to
Isolation.DEFAULT, which uses the database's default isolation level.- Returns:
- the isolation level
- Default:
DEFAULT
-
timeout
int timeoutThe transaction timeout in seconds.Defaults to -1 (no timeout / use database default). Only a value greater than 0 is ever acted on at all -- the interceptor skips calling
TransactionManager.setTimeout(int)entirely for -1 or 0, so both mean exactly "no timeout requested," on every backend.The bound is per statement against a shared, shrinking budget -- not a wall-clock limit on the method body. (D-10, Spring's approach.) When a value greater than 0 is set, the transaction gets a deadline
Nseconds out from when it began. Every individual JDBC statement issued afterward -- through the ORM's normal read/write methods, and through the direct batch paths ininsertAll/updateAll-- is given a query timeout equal to whatever time is left in that budget when the statement is prepared, floored at 1 second so an exhausted budget still fails fast rather than becoming unlimited (setQueryTimeout(0)means "no limit" in the JDBC contract). This is deliberately not a wall-clock bound on the method as a whole: non-database work inside the method (a slow computation, a network call to something else) is never interrupted, because plain JDBC has no mechanism to cancel work already in flight, and this framework does not add one (seeREQUIREMENTS.md's "out of scope" section).Per backend:
- SQLite, MySQL (JDBC-backed): enforced exactly as described above.
- JSON-backed: a positive value makes the transaction fail outright.
JsonTransactionManager.setTimeout(int)throwsUnsupportedOperationExceptionunconditionally, since a snapshot-based rollback has no query or connection to bound.
setTimeoutfor it.- Returns:
- the timeout in seconds
- Default:
-1
-
readOnly
boolean readOnlyWhether the transaction is read-only.A read-only transaction may allow database optimizations and is useful for methods that only query data.
- Returns:
- true if read-only
- Default:
false
-
rollbackFor
Exception types that should trigger a rollback, in addition to theRuntimeException/Errordefault.This is additive, not a replacement: an exception that matches neither
rollbackFornornoRollbackForstill falls through to the unchecked default, exactly as if neither attribute were set. When an exception matches both this andnoRollbackFor(), the rule whose listed class is the shallower inheritance-depth match to the thrown exception wins (Spring'sRuleBasedTransactionAttributetiebreak); on an exact-depth tie - including the same class appearing in both arrays - the transaction rolls back.- Returns:
- exception types to additionally rollback for
- Since:
- 6.3.0 additive combination with
noRollbackFor()and the depth tiebreak (D-06, D-07); before 6.3.0 a non-empty, unmatchedrollbackForsilently committed instead of falling through to the default.
- Default:
{}
-
noRollbackFor
Exception types that should NOT trigger a rollback, overriding theRuntimeException/Errordefault for those types.Combines with
rollbackFor()by shallowest-inheritance-depth match, described there; on an exact-depth tie the transaction still rolls back.- Returns:
- exception types to not rollback for
- Default:
{}
-