TransactionTemplate
is a nice tool for programmatic transaction. Should it supports RuleBasedTransactionAttribute
cause it is useful when we try to rollback for some Checked Exception which is similar to @Transactional(rollbackFor = Exception.class, noRollbackFor = IOException.class)
;
public void example() {
var transactionDefinition = new RuleBasedTransactionAttribute();
transactionDefinition.setRollbackRules(List.of(
new RollbackRuleAttribute(IOException.class)
));
TransactionTemplate template = new TransactionTemplate(transactionManager, transactionDefinition);
template.execute(status -> {
if (true) {
throw new IOException("io exception");
}
return null;
});
}
Currently, it will be rollback cause TransactionTemplate
will rollback on any Exceptions.
I am not sure supporting for RuleBasedTransactionAttribute
is a good idea, cuase there are non RollbackRuleAtribute
in class TransactionDefinition
which TransactionTemplate
is using, it only existed in RuleBasedTransactionAttribute
.
Here is a simple example that may solve this problem:
// TransactionTemplate.java
public TransactionTemplate(PlatformTransactionManager transactionManager, TransactionDefinition transactionDefinition) {
super(transactionDefinition);
this.transactionManager = transactionManager;
this.rollbackRules = null;
}
public TransactionTemplate(PlatformTransactionManager transactionManager, RuleBasedTransactionAttribute ruleBasedTransactionAttribute) {
super(ruleBasedTransactionAttribute);
this.transactionManager = transactionManager;
this.getRollbackRules = ruleBasedTransactionAttribute.getRollbackRules();
}
Comment From: snicoll
@tanyaofei thanks for the suggestion but I am not sure I got that. Can you share a little code snippet that would demonstrate what you mean?
Comment From: tanyaofei
@tanyaofei thanks for the suggestion but I am not sure I got that. Can you share a little code snippet that would demonstrate what you mean?
thx for your kind reply. Forgive for my confusing content, now it was updated.
Comment From: snicoll
Thanks for the reply. I didn't mean an implementation of the solution but rather what you currently are doing and how rollbackFor
and norollbackFor
would help. TransactionCallback
doesn't let you throw a checked exception so that code above doesn't compile.
In other words, I am wondering how such a feature would be helpful considering that you are already doing the exception management for checked exceptions.
Comment From: tanyaofei
I am working on a Advisor that can help our TCC transaction to make sure prepare
, commit
, rollback
runs ordered, this solution called TCC Fence
in some frameworks. For that, this advisor will open a transaction and insert a record into database before invoking the method(prepare, commit ,rollback). But most of time, method prepare
will annotated with @Transactional
, so my advisor should respect it and use it's configuration to open a transaction. For that the advisor will get the @Transactional
and transform it to a RuleBasedTransactionAttribute
and open a transaction.
Here is a simple example.
public void prepareFence(MethodInvocation invocation) throws Throwable {
Transactional transactional = AnnotationUtils.findAnnotation(invocation.getMethod(), Transactional.class);
RuleBasedTransactionAttribute txAttrs = new RuleBasedTransactionAttribute();
txAttrs.setIsolationLevel(transactional.isolation().value());
txAttrs.setPropagationBehavior(transactional.propagation().value());
txAttrs.setRollbackRules(Arrays.stream(transactional.rollbackFor())
.map(RollbackRuleAttribute::new)
.collect(Collectors.toList())
);
TransactionTemplate template = new TransactionTemplate(transactionManager, txAttrs);
AtomicReference<Throwable> exception = new AtomicReference<>();
template.execute(status -> {
if (insertLock("GlobalTransactionID")) {
try {
invocation.getMethod().invoke(invocation.getArguments());
} catch(Throwable e) {
if (txAttrs.rollbackOn(e)) {
status.setRollbackOnly();
throw new IllegalStateException();
} else {
exception.set(e);
return null;
}
}
} else {
status.setRollbackOnly();
throw new IllegalStateException("Prepare failed cause the global transaction may rollbacked");
}
return null;
});
if (exception.get() != null) {
throw exception.get();
}
}
By now I am using RuleBasedTransactionAttribute#rollbackOn
and AtomicReference
to solve this problem. I just wondering may be TransactionTemplate
can do this for me?
Or there is an other way, I will just put my Advisor after TransactionInterceptor
so my code will run inside the transaction, but, still, I just wondering if TransactionTempalte
needs to have this feature.
Comment From: sbrannen
Hi @tanyaofei,
Congratulations on opening your first issue for the Spring Framework! 👍
For that, this advisor will open a transaction and insert a record into database before invoking the method(prepare, commit ,rollback).
If you're saying that you want to invoke an additional prepare()
method prior to invoking the "standard business logic method", you will indeed need to handle that separately.
Whether or not that prepare()
method needs to be invoked with customizable transaction attributes (for example, via @Transactional
) is up to you.
But most of time, method
prepare
will annotated with@Transactional
, so my advisor should respect it and use it's configuration to open a transaction. For that the advisor will get the@Transactional
and transform it to aRuleBasedTransactionAttribute
and open a transaction.
Please note that manually looking up @Transactional
and parsing it to create an instance of RuleBasedTransactionAttribute
is not generally something we would advise. Instead, we typically recommend the use of AnnotationTransactionAttributeSource
for such use cases.
By now I am using
RuleBasedTransactionAttribute#rollbackOn
andAtomicReference
to solve this problem. I just wondering may beTransactionTemplate
can do this for me?
No, TransactionTemplate
cannot do that for you. As @snicoll mentioned, the TransactionCallback
that you pass to TransactionTemplate#execute(TransactionCallback)
cannot throw a checked exception. Thus, you will manually have to implement your catch(Throwable e)
support within your own TransactionCallback
implementation, similar to what you've done in the example you provided.
Or there is an other way, I will just put my Advisor after
TransactionInterceptor
so my code will run inside the transaction
If that satisfies the needs of your use case, I would probably go with that.
I just wondering if
TransactionTempalte
needs to have this feature.
TransactionTemplate
has built-in, default support for handling rollbacks. See TransactionTemplate#rollbackOnException
for details. If that does not meet your needs, you will need to handle the exceptions in some custom manner.
In summary, I do not think it makes sense to attempt to integrate first-class support for rollbackFor
and noRollbackFor
in TransactionTemplate
, especially since a TransactionCallback
cannot throw a check exception.
In light of that, I am closing this issue.
If you have further questions about how to address your use case, those would be better suited to Stack Overflow. Feel free to update this issue with a link to the re-posted question (so that other people can find it).
Cheers,
Sam