无法在 Spring 引导应用程序的方面回滚数据库更改

Not able to rollback DB changes in Aspect in Spring boot application

我已经围绕一项服务编写了一个方面 class。方面,我在做before部分的一些操作,如果封装的服务方法出现异常,我想回滚。

服务class如下:

@Service
@Transactional
class ServiceA {
   ...
   public void doSomething() {
      ...
   }
   ...
}

看点如下:

  @Aspect
  @Order(2)
  public class TcStateManagementAspect {
  
  ...

  @Around(value = "applicationServicePointcut()", argNames = "joinPoint")
  public Object process(ProceedingJoinPoint joinPoint)
      throws Throwable {
    ...
    */Before section */

    do some processing and persist in DB
    ...
    Object object = joinPoint.proceed();
    ...
    do some post-processing
  }
}

我在 Begin Section 中看到服务方法中的异常未回滚数据库操作。我试着把 @Transactional 放在 @Around 上,但没有用。

在这种情况下,我浏览了以下帖子:

  1. Spring @Transactional in an Aspect (AOP)

但是对于如何实现这一点,我无法得到任何具体的想法。有人可以在这里帮忙吗?谢谢。

正如我在评论中所说,您的 around 建议所做的也必须声明为事务性的。您不能直接这样做,因为 @Transactional 在内部通过动态代理使用 Spring AOP。但是,Spring AOP方面不能成为其他方面的目标。但是您可以简单地创建一个新的助手 @Component,您将建议的操作委托给它。

让我们假设目标是记录您的方面所针对的 @Transactional 方法的参数。然后只需这样做:

package com.example.managingtransactions;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class TxLogAspect {
  private final static Logger logger = LoggerFactory.getLogger(TxLogAspect.class);

  @Autowired
  TxLogService txLogService;

  @Pointcut(
    "@annotation(org.springframework.transaction.annotation.Transactional) && " +
    "!within(com.example.managingtransactions.TxLogService)"
  )
  public void applicationServicePointcut() {}

  @Around("applicationServicePointcut()")
  public Object process(ProceedingJoinPoint joinPoint) throws Throwable {
    logger.info(joinPoint.toString());
    // Delegate to helper component in order to be able to use @Transactional
    return txLogService.logToDB(joinPoint);
  }
}
package com.example.managingtransactions;

import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.util.Arrays;
import java.util.List;

/**
 * Helper component to delegate aspect advice execution to in order to make the
 * advice transactional.
 * <p>
 * Aspect methods themselves cannot be @Transactional, because Spring AOP aspects
 * cannot be targeted by other aspects. Delegation is a simple and elegant
 * workaround.
 */
@Component
public class TxLogService {
  @Autowired
  private JdbcTemplate jdbcTemplate;

  @Transactional
  public Object logToDB(ProceedingJoinPoint joinPoint) throws Throwable {
    jdbcTemplate.update(
      "insert into TX_LOG(MESSAGE) values (?)",
      Arrays.deepToString(joinPoint.getArgs())
    );
    return joinPoint.proceed();
  }

  public List<String> findAllTxLogs() {
    return jdbcTemplate.query(
      "select MESSAGE from TX_LOG",
      (rs, rowNum) -> rs.getString("MESSAGE")
    );
  }
}

看到了吗?我们通过连接点实例传递给辅助组件自己的 @Transactional 方法,这意味着事务在进入该方法时启动,并根据 joinPoint.proceed() 的结果提交或回滚。 IE。如果方面的目标方法出现问题,方面助手写入数据库本身的内容也将回滚。

顺便说一句,因为我以前从未使用过Spring事务,所以我只是简单地从https://spring.io/guides/gs/managing-transactions/中获取示例并添加了上面的两个类。之前,我也将此添加到 schema.sql:

create table TX_LOG(ID serial, MESSAGE varchar(255) NOT NULL);

接下来,我添加确保 TxLogService 被注入 AppRunner:

  private final BookingService bookingService;
  private final TxLogService txLogService;

  public AppRunner(BookingService bookingService, TxLogService txLogger) {
    this.bookingService = bookingService;
    this.txLogService = txLogger;
  }

如果那么在AppRunner.run(String...)的末尾添加这两个语句

    logger.info("BOOKINGS: " + bookingService.findAllBookings().toString());
    logger.info("TX_LOGS: " + txLogService.findAllTxLogs().toString());

您应该在控制台日志的末尾看到类似这样的内容:

c.e.managingtransactions.AppRunner       : BOOKINGS: [Alice, Bob, Carol]
c.e.managingtransactions.AppRunner       : TX_LOGS: [[[Alice, Bob, Carol]]]

即您会看到只有成功的预订交易才会将日志消息写入数据库,而不是两个失败的交易。