Обработка событий spring -data-rest в транзакции

Мне нужно публиковать уведомления о событиях для внешних систем через JMS, когда данные обновляются. Id, как это делается в рамках той же транзакции, что и объекты, предназначенные для базы данных для обеспечения целостности.

События ApplicationLifecycle, которые выделяют spring -data-rest, выглядят как логическое место для реализации этой логики.

@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");
    }

}

Однако эти события происходят до и после начала и фиксации tx.

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 

В какой точке расширения есть spring -data-rest для добавления поведения, которое будет выполняться в рамках управляемой транзакции spring?

Ответ 1

Я использую aop (pointcut и tx advice) для решения этой проблемы:

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

и 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>

Это то же самое, что и методы контроллера, аннотированные с помощью @Transactional.

Ответ 2

Я не работал над spring -data-rest, но с spring, это можно обработать следующим образом.

1) Определите настраиваемый TransactionSynchronizationAdapter и зарегистрируйте bean в TransactionSynchronizationManager.

Обычно у меня есть метод registerSynchronizaiton с @Before pointcut для этого.

@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) И, реализуйте метод Override следующим образом

@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
    }
}