sara
sara

Reputation: 1170

Spring: automatic rollback on checked exceptions

One way to configure Spring to rollback on a non RuntimeExceptions is using @Transactional(rollbackFor=...) annotation on the service classes. The problem with this approach is that we need to define (rollbackFor=...) for almost all the service classes which seems really redundant.


My question: Is there any way to configure a default behaviour for Spring transaction manager to rollback on a non RuntimeException whenever it happens without declaring it on every @Transactional annotation. Something like using @ApplicationException(rollback=true) annotation on an exception class in EJB.

Upvotes: 18

Views: 11634

Answers (3)

jhyot
jhyot

Reputation: 3955

This is a similar approach as this answer, i.e. changing the default globally, but with as minimal change to Spring's config as possible, and still leaving the possibility to customize rollback rules per method as usual (with rollbackFor, noRollbackFor etc.).

This is achieved by simply adding a default RollbackRule for Exception.class. Since the rules have precedence according to the exception class hierarchy (the rule for the most specific exception class applicable wins), the new rule has basically lowest precendence, if no other rules are defined on the annotation.

@Configuration
public class MyTransactionManagementConfiguration {
  /**
   * Note: This custom config does NOT recognize {@code javax.transaction.Transactional} annotations in contrast to
   * the original Spring behaviour. Check the original {@code AnnotationTransactionAttributeSource} source code for an idea how to add that.
   *
   * @see AnnotationTransactionAttributeSource#AnnotationTransactionAttributeSource(boolean)
   */
  @Bean
  @Primary
  @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
  public TransactionAttributeSource transactionAttributeSourceWithDefaultRollBackForAllExceptions() {
    return new AnnotationTransactionAttributeSource(
        new SpringTransactionAnnotationParser() {
          
          @Override
          protected TransactionAttribute parseTransactionAnnotation(AnnotationAttributes attributes) {
            RuleBasedTransactionAttribute rbta = (RuleBasedTransactionAttribute) super.parseTransactionAnnotation(attributes);
            List<RollbackRuleAttribute> rules = new ArrayList<>(rbta.getRollbackRules());
            rules.add(new RollbackRuleAttribute(Exception.class));
            rbta.setRollbackRules(rules);
            return rbta;
          }

        }
    );
  }
}

Upvotes: 0

DAN
DAN

Reputation: 601

This config solves it:

@Configuration
public class MyProxyTransactionManagementConfiguration extends ProxyTransactionManagementConfiguration {

    @Bean
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    public TransactionAttributeSource transactionAttributeSource() {
        return new AnnotationTransactionAttributeSource() {

            @Nullable
            protected TransactionAttribute determineTransactionAttribute(AnnotatedElement element) {
                TransactionAttribute ta = super.determineTransactionAttribute(element);
                if (ta == null) {
                    return null;
                } else {
                    return new DelegatingTransactionAttribute(ta) {
                        @Override
                        public boolean rollbackOn(Throwable ex) {
                            return super.rollbackOn(ex) || ex instanceof Exception;
                        }
                    };
                }
            }
        };
    }
}

Upvotes: 12

xyz
xyz

Reputation: 5427

You can't do it for application level with @Transactional , but you can :

variant 1 : extend @Transactional annotation and put it as default value for rollbackfor. But set rollbackFor unchecked exceptions only that you need .With this you can control rollbacks only for case that you sure , and avoid copy past of @Transactional(rollbackFor =MyCheckedException.class)

Like:

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Transactional(rollbackFor=MyCheckedException.class)
public @interface TransactionalWithRollback {
}

And use this annotation instead of standard @Transactional.

variant 2 : you can create extension from AnnotationTransactionAttributeSource and override method determineTransactionAttribute:

protected TransactionAttribute  determineTransactionAttribute(AnnotatedElement ae)
//Determine the transaction attribute for the given method or class.

TransactionAttribute see TransactionAttribute api , there is a method

boolean rollbackOn(Throwable ex) Should we roll back on the given exception?

protected TransactionAttribute determineTransactionAttribute(
    AnnotatedElement ae) {
    return new DelegatingTransactionAttribute(target) {
        @Override
        public boolean rollbackOn(Throwable ex) {
           return (check is exception type as you need for rollback );
       }
};

}

Second approach is not so good as first as you do it really global for transaction manager. Better use custom annotation as you can control it any apply only for methods/classes where you really need it. But if you need it in any case use second variant , it will be your default transnational behavior.

Upvotes: 13

Related Questions