Class TransactionInterceptor
- All Implemented Interfaces:
MethodInterceptor
This interceptor processes @Transactional annotations and manages transaction boundaries accordingly. Each propagation is handled as follows:
REQUIRED: begins a new transaction when none is active; otherwise joins the active one as a participant (below).REQUIRES_NEW: suspends whatever is active, runs in a new, independent transaction, and resumes the suspended one afterwards.SUPPORTS: joins an active transaction as a participant; without one, the method runs with no transaction.MANDATORY: joins an active transaction as a participant; without one, it throwsIllegalStateExceptionand the method does not run.NOT_SUPPORTED: suspends whatever is active, runs with no transaction, and resumes the suspended one afterwards.NEVER: throwsIllegalStateExceptionwhen a transaction is active; otherwise runs with no transaction.
A joined REQUIRED, SUPPORTS or MANDATORY method runs in a participation
scope: begin(), then commit() when it returns, or rollback() when it
throws an exception its own rollbackFor/noRollbackFor rules accept (otherwise
commit()); its exception is rethrown unchanged. This relies on the
TransactionManager nesting contract both built-in managers implement:
begin() on an active transaction joins it, rollback() at a nested depth marks the
transaction rollback-only without ending it, and the outermost commit() of a marked
transaction rolls back and throws
UnexpectedRollbackException. So a joined method's
failure that its caller catches still rolls the whole transaction back, as Spring does by default
(AbstractPlatformTransactionManager's globalRollbackOnParticipationFailure = true;
the participant's rollback-or-commit choice follows TransactionAspectSupport's
completeTransactionAfterThrowing). A joined method's isolation, read-only and timeout
settings are not applied; they belong to the transaction that began it.
For a new transaction, the commit after a normal return runs after, not inside, the body's
failure handler (TransactionAspectSupport's invokeWithinTransaction shape), so the
transaction is completed once: a failing commit reaches the caller as it is, and when the
method's rules commit despite its exception and that commit fails, the body's exception is kept
on the commit failure as suppressed.
- Since:
- 6.2.0
- Author:
- wisdomme
-
Field Summary
FieldsModifier and TypeFieldDescriptionprivate static final Loggerprivate final AnnotationLookupCache<Transactional> Instance-scoped on purpose - see AnnotationLookupCache's class javadoc.private final TransactionManager -
Constructor Summary
ConstructorsConstructorDescriptionTransactionInterceptor(TransactionManager transactionManager) Creates a new transaction interceptor. -
Method Summary
Modifier and TypeMethodDescriptionprivate voidapplySettings(Transactional tx, String methodName) Applies the isolation, read-only and timeout settings oftxto the transaction this invocation has just begun.private ObjectexecuteAsParticipant(MethodInvocation invocation, Transactional tx) Executes the invocation as a participant in the transaction already active on this thread.private ObjectexecuteInNewTransaction(MethodInvocation invocation, Transactional tx) Executes the invocation in a new transaction.private ObjectexecuteWithTransaction(MethodInvocation invocation, Transactional tx) Executes the method invocation within a transaction context.private intinheritanceDepth(Class<?> thrownClass, Class<?> ruleClass) WalksthrownClass's superclass chain to find the depth at which it matchesruleClass.invoke(MethodInvocation invocation) Intercept the given method invocation.private IntegershallowestMatchDepth(Throwable thrown, Class<? extends Throwable>[] rules) Returns the shallowest inheritance depth at whichthrown's runtime class matches any rule inrules, ornullif none match.private booleanshouldRollback(Throwable e, Transactional tx) Determines if the transaction should be rolled back for the given exception.
-
Field Details
-
lookupCache
Instance-scoped on purpose - see AnnotationLookupCache's class javadoc. -
LOGGER
-
transactionManager
-
-
Constructor Details
-
TransactionInterceptor
Creates a new transaction interceptor.- Parameters:
transactionManager- the transaction manager to use
-
-
Method Details
-
invoke
Description copied from interface:MethodInterceptorIntercept the given method invocation.Implementations should call
MethodInvocation.proceed()to continue the interceptor chain or invoke the target method.- Specified by:
invokein interfaceMethodInterceptor- Parameters:
invocation- the method invocation context- Returns:
- the result of the method invocation
- Throws:
Throwable- if the interceptor or target method throws an exception
-
executeWithTransaction
private Object executeWithTransaction(MethodInvocation invocation, Transactional tx) throws Throwable Executes the method invocation within a transaction context.- Throws:
Throwable
-
executeInNewTransaction
private Object executeInNewTransaction(MethodInvocation invocation, Transactional tx) throws Throwable Executes the invocation in a new transaction.Only the body runs inside the
trywhose handler completes the transaction after a failure; the commit after a normal return runs after that handler, so a failing commit (UnexpectedRollbackExceptionincluded) reaches the caller as it is and is never followed by a second completion call. When the body's exception does not call for a rollback and the commit then fails, the commit failure is thrown with the body's exception attached as suppressed.- Throws:
Throwable
-
executeAsParticipant
Executes the invocation as a participant in the transaction already active on this thread.begin()joins the active transaction (one more nesting level). When the body returns,commit()leaves that level. When the body throws an exception the method's own rules accept (shouldRollback(java.lang.Throwable, com.ultikits.ultitools.annotations.Transactional)),rollback()at the nested level marks the whole transaction rollback-only without ending it; any other exception leaves it withcommit(). The body's exception is always rethrown unchanged, with a failure of that completion call attached as suppressed. The method's isolation, read-only and timeout settings are not applied: they belong to the transaction that began it.- Throws:
Throwable
-
applySettings
Applies the isolation, read-only and timeout settings oftxto the transaction this invocation has just begun.When applying a setting fails, the transaction this invocation began is rolled back once, so its connection is released and no active context is left on the thread for the next call to join; any failure of that rollback is attached to the settings failure with
Throwable.addSuppressed(Throwable), and the settings failure itself is rethrown unchanged. The method body never runs. AREQUIRES_NEWcaller still resumes the suspended outer transaction in its ownfinally. This mirrors Spring'sDataSourceTransactionManager.doBegin, which applies these settings inside itstryand releases the connection before rethrowing.- Parameters:
tx- the transaction attributes of the invoked methodmethodName- the qualified method name, for logging
-
shouldRollback
Determines if the transaction should be rolled back for the given exception.Additive, depth-ordered combination of
rollbackForandnoRollbackFor(D-06/D-07 - confirmed by the maintainer during 02-06's checkpoint, matching Spring'sRuleBasedTransactionAttribute): each array is checked for its shallowest matching inheritance depth viashallowestMatchDepth(Throwable, Class[]). When both arrays match, the shallower depth wins; an exact-depth tie - including the same exception class listed in both arrays - favours rollback. When only one array matches, that one decides. When neither matches (including when both arrays are empty), this falls through to the unchangedRuntimeException/Errordefault - a non-emptyrollbackForwith no match no longer short-circuits to "commit" the way it did before this fix. -
shallowestMatchDepth
Returns the shallowest inheritance depth at whichthrown's runtime class matches any rule inrules, ornullif none match. Depth 0 is an exact class match; each step upthrown's superclass chain toward a matching rule adds 1. -
inheritanceDepth
WalksthrownClass's superclass chain to find the depth at which it matchesruleClass. Callers only reach this after confirmingruleClass.isInstance(...)of an instance ofthrownClassalready holds, so the walk is guaranteed to terminate atruleClassrather than atnull.
-