Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Handle spring-data-rest application events within the transaction

I need to publish notification events to external systems over JMS, when data is updated. Id like this to be done within the same transaction as the objects are committed to the database to ensure integrity.

The ApplicationLifecycle events that spring-data-rest emits seemed like the logical place to implement this logic.

@org.springframework.transaction.annotation.Transactional
public class TestEventListener extends AbstractRepositoryEventListener<Object> {

    private static final Logger LOG = LoggerFactory.getLogger(TestEventListener.class);

    @Override
    protected void onBeforeCreate(Object entity) {
        LOG.info("XXX before create");
    }

    @Override
    protected void onBeforeSave(Object entity) {
        LOG.info("XXX before save");
    }

    @Override
    protected void onAfterCreate(Object entity) {
        LOG.info("XXX after create");
    }

    @Override
    protected void onAfterSave(Object entity) {
        LOG.info("XXX after save");
    }

}

However, these events happen before and after the tx starts and commits.

08 15:32:37.119 [http-nio-9000-exec-1] INFO  n.c.v.vcidb.TestEventListener - XXX before create 
08 15:32:37.135 [http-nio-9000-exec-1] TRACE o.s.t.i.TransactionInterceptor - Getting transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.save]



08 15:32:37.432 [http-nio-9000-exec-1] TRACE o.s.t.i.TransactionInterceptor - Completing transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.save] 
08 15:32:37.479 [http-nio-9000-exec-1] INFO  n.c.v.vcidb.TestEventListener - XXX after create 

What extension point does spring-data-rest have for adding behaviour that will execute within the spring managed transaction?

like image 472
dan carter Avatar asked Sep 08 '14 03:09

dan carter


People also ask

How is event handling done in spring?

Spring's event handling is single-threaded so if an event is published, until and unless all the receivers get the message, the processes are blocked and the flow will not continue. Hence, care should be taken when designing your application if the event handling is to be used.

What is ApplicationListener in spring?

As of Spring 3.0, an ApplicationListener can generically declare the event type that it is interested in. When registered with a Spring ApplicationContext , events will be filtered accordingly, with the listener getting invoked for matching event objects only.

What is ApplicationEventPublisher?

Interface ApplicationEventPublisher This is a functional interface and can therefore be used as the assignment target for a lambda expression or method reference. @FunctionalInterface public interface ApplicationEventPublisher. Interface that encapsulates event publication functionality.

What is used for exposing spring data repositories over rest using spring Data rest?

Spring Data REST can be used to expose HATEOAS RESTful resources around Spring Data repositories. Without writing a lot of code, we can expose RESTful API around Spring Data Repositories.


2 Answers

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.

like image 137
phlebas Avatar answered Sep 20 '22 17:09

phlebas


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);
                    }
                }
            });
        }
    }
like image 21
shenyu1997 Avatar answered Sep 18 '22 17:09

shenyu1997