处理事务中的 spring-data-rest 应用程序事件

Posted

技术标签:

【中文标题】处理事务中的 spring-data-rest 应用程序事件【英文标题】:Handle spring-data-rest application events within the transaction 【发布时间】:2014-11-01 06:02:36 【问题描述】:

当数据更新时,我需要通过 JMS 向外部系统发布通知事件。我希望在将对象提交到数据库时在同一事务中完成此操作以确保完整性。

spring-data-rest 发出的 ApplicationLifecycle 事件似乎是实现此逻辑的合乎逻辑的地方。

@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 托管事务中执行的行为?

【问题讨论】:

你明白了吗?现在面临同样的问题。 恐怕目前还没有解决方案。 我在这里遇到了同样的问题。如果你们有任何有用的东西,请发帖@Daniel 【参考方案1】:

我使用 aop(切入点和 tx 建议)来解决这个问题:

@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 注释的控制器方法相同。

【讨论】:

当你在 PUTing text/uri-list 来更新一个嵌入的属性时,你还需要在你的 AOP 配置中包含 org.springframework.data.rest.webmvc.RepositoryPropertyReferenceController.createPropertyReference 刚刚尝试过这个,因为我在使用 Spring Boot 2.1.0 和 Neo4j 时遇到了同样的问题。不幸的是,由于一些 CGLIB 问题,这个解决方案似乎不适用于我的情况。【参考方案2】:

phlebas 描述的解决方案有效。而且我还认为“在同一事务中运行事件处理程序”应该是 Spring Data Rest 应该提供的功能。有很多常见的用例需要拆分逻辑来分离 eventHandler。就像“数据库中的触发器”一样。下面显示的版本与 phlebas 解决方案相同。

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

【讨论】:

【参考方案3】:

我没有研究过spring-data-rest,但是有了spring,可以通过以下方式处理。

1) 定义自定义TransactionSynchronizationAdapter,并在TransactionSynchronizationManager 中注册bean。

通常,我有一个方法 registerSynchronizaiton 和一个 @Before 切入点。

@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
    

【讨论】:

有了这个方法,我们在哪里定义我们想要包装在事务中的方法? 抱歉回复太晚了,需要事务同步的方法使用AOP

以上是关于处理事务中的 spring-data-rest 应用程序事件的主要内容,如果未能解决你的问题,请参考以下文章

是否有适用于 JPA、spring-data、spring-data-rest 的通用 REST 查询语言

Spring-Data-Rest 验证器

如何在 Spring-Data-Rest 中实现细粒度的访问控制?

使用 spring-data-rest 将资源添加到集合

排除 Spring-data-rest 资源的部分字段

将业务逻辑添加到 spring-data-rest 应用程序