【问题标题】:Not able to rollback DB changes in Aspect in Spring boot application无法在 Spring Boot 应用程序中回滚 Aspect 中的数据库更改
【发布时间】:2021-10-12 12:30:03
【问题描述】:

我围绕服务类编写了一个方面。在这方面,我在前面部分做了一些操作,如果封闭的服务方法发生异常,我想回滚。

服务类如下:

@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)
  2. Custom Spring AOP Around + @Transactional

但我无法获得有关如何实现这一目标的任何具体想法。有人可以帮忙吗?谢谢。

【问题讨论】:

  • 事务在输入注解的方法时开始(即调用proceed()时),而不是之前。我不是 Spring 用户,但您是否尝试过让切面建议方法也具有事务性?

标签: java spring-boot aspectj spring-aop spring-transactions


【解决方案1】:

就像我在评论中所说,您的周围建议所做的也必须声明为事务性的。您不能直接这样做,因为@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")
    );
  }
}

看到了吗?我们通过 joinpoint 实例传递给辅助组件自己的@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;
  }

如果 then 在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]]]

即您会看到,只有成功的预订事务才会向 DB 写入日志消息,而不是两个失败的事务。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-03-03
    • 1970-01-01
    • 2014-12-25
    • 2020-04-16
    • 1970-01-01
    • 1970-01-01
    • 2016-11-30
    相关资源
    最近更新 更多