Handle spring-data-rest application events within the transaction

依然范特西╮ 提交于 2019-11-28 08:26:22

I use aop (pointcut and tx advice) to solve this problem:

@Configuration
@ImportResource("classpath:/aop-config.xml")
public class AopConfig { ...

and aop-config.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/beans   http://www.springframework.org/schema/beans/spring-beans.xsd
                      http://www.springframework.org/schema/aop     http://www.springframework.org/schema/aop/spring-aop.xsd
                      http://www.springframework.org/schema/tx      http://www.springframework.org/schema/tx/spring-tx.xsd"
    default-autowire="byName">

    <aop:config>
        <aop:pointcut id="restRepositoryTx"
            expression="execution(* org.springframework.data.rest.webmvc.RepositoryEntityController.*(..))" />
        <aop:advisor id="managerTx" advice-ref="txAdvice" pointcut-ref="restRepositoryTx" order="20" />
    </aop:config>

    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="postCollectionResource*" propagation="REQUIRES_NEW" rollback-for="Exception" />
            <tx:method name="putItemResource*" propagation="REQUIRES_NEW" rollback-for="Exception" />
            <tx:method name="patchItemResource*" propagation="REQUIRES_NEW" rollback-for="Exception" />
            <tx:method name="deleteItemResource*" propagation="REQUIRES_NEW" rollback-for="Exception" />
            <!-- <tx:method name="*" rollback-for="Exception" /> -->
        </tx:attributes>
    </tx:advice>

</beans>

This is the same as having controller methods annotated with @Transactional.

The solution described by phlebas work. And I also think "Run event handler within a same transaction" should be a feature which should be provided by Spring Data Rest. There are many common use cases to need to split logic to sepreate eventHandler. just like "triggers in database". The version show below is same as phlebas solution.

    @Aspect
    @Component
    public class SpringDataRestTransactionAspect {

        private TransactionTemplate transactionTemplate;

        public SpringDataRestTransactionAspect(PlatformTransactionManager transactionManager) {
            this.transactionTemplate = new TransactionTemplate(transactionManager);
            this.transactionTemplate.setName("around-data-rest-transaction");
        }

        @Pointcut("execution(* org.springframework.data.rest.webmvc.*Controller.*(..))")
        public void aroundDataRestCall(){}

        @Around("aroundDataRestCall()")
        public Object aroundDataRestCall(ProceedingJoinPoint joinPoint) throws Throwable {
            return transactionTemplate.execute(transactionStatus -> {
                try {
                    return joinPoint.proceed();
                } catch (Throwable e) {
                    transactionStatus.setRollbackOnly();
                    if(e instanceof RuntimeException) {
                        throw (RuntimeException)e;
                    } else {
                        throw new RuntimeException(e);
                    }
                }
            });
        }
    }

I have not worked on spring-data-rest, but with spring, this can be handled the following way.

1) Define custom TransactionSynchronizationAdapter, and register the bean in TransactionSynchronizationManager.

Usually, I have a method registerSynchronizaiton with a @Before pointcut for this.

@SuppressWarnings("rawtypes") @Before("@annotation(org.springframework.transaction.annotation.Transactional)")
    public void registerSynchronization() {
        // TransactionStatus transStatus = TransactionAspectSupport.currentTransactionStatus();
        TransactionSynchronizationManager.registerSynchronization(this);
        final String transId = UUID.randomUUID().toString();
        TransactionSynchronizationManager.setCurrentTransactionName(transId);
        transactionIds.get().push(transId);
        if (TransactionSynchronizationManager.isActualTransactionActive() && TransactionSynchronizationManager
            .isSynchronizationActive() && !TransactionSynchronizationManager.isCurrentTransactionReadOnly()) {
            if (!TransactionSynchronizationManager.hasResource(KEY)) {
                final List<NotificationPayload> notifications = new ArrayList<NotificationPayload>();
                TransactionSynchronizationManager.bindResource(KEY, notifications);
            }
        }
    }

2) And, implement Override method as follows

@Override public void afterCompletion(final int status) {
    CurrentContext context = null;
    try {
        context = ExecutionContext.get().getContext();
    } catch (final ContextNotFoundException ex) {
        logger.debug("Current Context is not available");
        return;
    }
    if (status == STATUS_COMMITTED) {
        transactionIds.get().removeAllElements();
        publishedEventStorage.sendAllStoredNotifications();
        // customize here for commit actions
    } else if ((status == STATUS_ROLLED_BACK) || (status == STATUS_UNKNOWN)) {
       // you can write your code for rollback actions
    }
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!