Affects: 5.2.3.RELEASE
I have replicated the issue with 5.2.3.RELEASE
, but I believe later versions are also affected.
What I'm trying to do
I need a transaction which will not be rolled back if a RuntimeException
is thrown, but I need to do this manually (i.e. using a TransactionTemplate
) rather than using the @Transactional
annotation.
In summary, I am trying to achieve this behaviour...
@Transactional(dontRollbackOn = RuntimeException.class)
public void myMethod() { }
... using a TransactionTemplate
.
Issue description:
I have tried to achieve the behaviour outlined above by defining a new NoRollbackRuleAttribute
and constructing a TransactionTemplate
with it:
RuleBasedTransactionAttribute rules = new RuleBasedTransactionAttribute();
rules.getRollbackRules().add(new NoRollbackRuleAttribute(RuntimeException.class));
TransactionTemplate transactionTemplateNoRollback = new TransactionTemplate(platformTransactionManager,rules);
CustomObject = transactionTemplateNoRollback.execute(
(TransactionCallback<CustomObject>) status -> {
throw new RuntimeException();
});
The NoRollbackRuleAttribute
seems to be ignored and an UnexpectedRollbackException
is thrown if my method my method throws any RuntimeException
:
org.springframework.transaction.UnexpectedRollbackException: Transaction silently rolled back because it has been marked as rollback-only
(Possible) cause
The TransactionTemplate(PlatformTransactionManager transactionManager, TransactionDefinition transactionDefinition)
constructor invokes the DefaultTransactionDefinition(TransactionDefinition other)
constructor which looks as follows:
public DefaultTransactionDefinition(TransactionDefinition other) {
this.propagationBehavior = other.getPropagationBehavior();
this.isolationLevel = other.getIsolationLevel();
this.timeout = other.getTimeout();
this.readOnly = other.isReadOnly();
this.name = other.getName();
}
Neither constructor seems to do anything with the RollbackRules provided to it.
*** Work around***
I have fallen back to using @Transactional(dontRollbackOn = RuntimeException.class)
for now, but that requires me to create a new public method within a different class which is exactly what I was trying to avoid.
Comment From: joeltoby
If this behaviour is correct / to be expected, is there another way to achieve the desired behaviour short of applying a custom TransactionInterceptor with AOP magic?
Comment From: jhoeller
This is by design: TransactionTemplate
does not support custom rollback rules. Technically TransactionTemplate
isn't even aware of TransactionAttribute
since that variant is an extended definition only supported by TransactionInterceptor
(and therefore also living in the latter's package). The template operates on plain TransactionDefinition
and uses fixed rollback behavior for all exceptions thrown from its callbacks, effectively RuntimeExceptions
and Errors
.
Alternatively, you may use the PlatformTransactionManager
directly: Inject it, call getTransaction
on it, perform some resource operations, then call commit
in a finally block... and handle potential runtime exceptions any way you need to, without calling rollback
. You may also selectively handle the outcome, as long as you eventually call commit
(or rollback
, for that matter) to complete the transaction.
Comment From: joeltoby
Thanks for the helpful and quick reply Juergen.
Comment From: Jiiiiiin
可以尝试做一个容器在 tmpl 模版之外来接受正常/异常,从而实现 异常的区别处理。