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 a RuleBasedTransactionAttribute 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 and AtomicReference to solve this problem. I just wondering may be TransactionTemplate 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