Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I ignore spring @Transactional annotation for a specific method when @ActiveProfiles("test")

I need to ignore the following @Transactional annotation during my integration tests.

@Service
public class MyClass {

    @Transactional(propagation = Propagation.NEVER)
    public void doSomething() {
        // do something that once in production can not be inside a transaction (reasons are omitted)
    }

}

The problem is that all my tests are executed inside a transaction that is rolled back by default. How could I ignore the @Transactional(propagation = Propagation.NEVER) annotation for this method when it is running in the scope of a test (@ActiveProfiles("test")) allowing it to be executed inside a transaction?

like image 478
vhtc Avatar asked Mar 09 '16 21:03

vhtc


1 Answers

First of all, you need to exclude your current @EnableTransactionManagement annotation to be active in your test profile. You can do this by isolating the @EnableTransactionManagement annotation to a separate configuration class which excludes the profile test so it only gets activated whenever the test profile is not active.

@EnableTransactionManagement(mode=AdviceMode.PROXY)
@Profile("!test")
public class TransactionManagementConfig {}

With that in place, we can start building up the custom transaction management configuration for your test profile. First we define an annotation that will be used to activate custom transaction management (javadoc comments stripped for compactness of the example, see EnableTransactionManagement javadoc for details).

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(CustomTransactionManagementConfigurationSelector.class)
public @interface EnableCustomTransactionManagement {
    boolean proxyTargetClass() default false;
    AdviceMode mode() default AdviceMode.PROXY;
    int order() default Ordered.LOWEST_PRECEDENCE;
}

Then we need an import selector. Note, that since you're using AdviceMode.PROXY, i skipped implementing the ASPECTJ part, but that should be done analogously in order to use AspectJ based transaction management.

public class CustomTransactionManagementConfigurationSelector extends
        AdviceModeImportSelector<EnableCustomTransactionManagement> {
    @Override
    protected String[] selectImports(AdviceMode adviceMode) {
        switch (adviceMode) {
        case PROXY:
            return new String[] { 
                AutoProxyRegistrar.class.getName(),
                CustomTransactionAttributeSourceConfig.class.getName()
            };
        case ASPECTJ:
        default:
            return null;
        }
    }
}

And finally the part where you will be able to override the transaction attributes. This one subclasses ProxyTransactionManagementConfiguration for AdviceMode.PROXY, you would need an analogous implementation based on AspectJTransactionManagementConfiguration for AdviceMode.ASPECTJ. Feel free to implement your own logic, whether be it a constant override of whatever attributes the original AnnotationTransactionAttributeSource would determine as in my example, or going to greater lengths by introducing and handling your own custom annotation for this purpose.

@Configuration
public class CustomTransactionAttributeSourceConfig
        extends ProxyTransactionManagementConfiguration {

    @Override
    public void setImportMetadata(AnnotationMetadata importMetadata) {
        this.enableTx = AnnotationAttributes
                .fromMap(importMetadata.getAnnotationAttributes(
                        EnableCustomTransactionManagement.class.getName(),
                        false));
        Assert.notNull(this.enableTx,
                "@EnableCustomTransactionManagement is not present on importing class "
                        + importMetadata.getClassName());
    }

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

            private static final long serialVersionUID = 1L;

            @Override
            protected TransactionAttribute findTransactionAttribute(
                    Class<?> clazz) {
                TransactionAttribute transactionAttribute = 
                        super.findTransactionAttribute(clazz);
                if (transactionAttribute != null) {
                    // implement whatever logic to override transaction attributes 
                    // extracted from @Transactional annotation
                    transactionAttribute = new DefaultTransactionAttribute(
                            TransactionAttribute.PROPAGATION_REQUIRED);
                }
                return transactionAttribute;
            }

            @Override
            protected TransactionAttribute findTransactionAttribute(
                    Method method) {
                TransactionAttribute transactionAttribute = 
                        super.findTransactionAttribute(method);
                if (transactionAttribute != null) {
                    // implement whatever logic to override transaction attributes
                    // extracted from @Transactional annotation
                    transactionAttribute = new DefaultTransactionAttribute(
                            TransactionAttribute.PROPAGATION_REQUIRED);
                }
                return transactionAttribute;
            }
        };
    }
}

Finally, you'll need to enable the custom transaction management configuration with a configuration class tied to the test profile.

@EnableCustomTransactionManagement(mode=AdviceMode.PROXY)
@Profile("test")
public class TransactionManagementTestConfig {}

I hope this helps.

like image 192
Nándor Előd Fekete Avatar answered Oct 15 '22 04:10

Nándor Előd Fekete