如何在 Spring Boot aop @Around 函数中创建事务?

How to create transaction in Spring Boot aop @Around function?

我想使用Spring Boot AOP 来实现授权方法。最初的想法是,如果来自 REST 调用的 return 对象 return 没有通过授权检查,它将抛出一个未经授权的异常。

我是这样做的:

@Aspect
@Component
public class AuthAspect {
  @Around("AllRestExecPoint()")
  public Object auth(ProceedingJoinPoint point) throws Throwable {
    Object returnObject = point.proceed();
    if (!checkAuthorization(returnObject)) {
      throw new UnauthException();
    }
    return returnObject;
  }
}

但是,问题是,如果此 REST 服务将对我的数据库执行一些插入或更新,它将在我的授权检查之前提交。因此,UnauthException 将被抛出,但事务仍会提交。

第一次尝试我想在proceed()调用之前手动创建事务并在return之前提交,但是失败了。

@Aspect
@Component
public class AuthAspect {
  private final EntityManager em;

  @Autowired
  public AuthAspect(EntityManager em) {
    this.em = em;
  }

  @Around("AllRestExecPoint()")
  public Object auth(ProceedingJoinPoint point) throws Throwable {
    em.getTransaction().begin();

    Object returnObject = point.proceed();
    if (!checkAuthorization(returnObject)) {
      throw new UnauthException();
    }

    em.getTransaction().commit();

    return returnObject;
  }
}

它会导致java.lang.IllegalStateException: Not allowed to create transaction on shared EntityManager - use Spring transactions or EJB CMT instead

我在网上搜索了一些答案需要修改web.xml文件,但是我不想用xml来配置

从您使用的标签判断您正在使用 SpringBoot. Spring Boot 提供了一个预配置的 TransactionTemplate 如果您想要手动控制事务,您可以使用它。

而不是 EntityManger 将其注入您的方面并将您的代码包装在其中。

@Aspect
@Component
public class AuthAspect {
  private final TransactionTemplate tx;

  public AuthAspect(TransactionTemplate tx) {
    this.tx = tx;
  }

  @Around("AllRestExecPoint()")
  public Object auth(ProceedingJoinPoint pjp) throws Throwable {

    return tx.execute(ts -> this.executeAuth(pjp));   
  }

  private Object executeAuth(ProceedingJoinPoint pjp) {
    Object returnObject;
    try {
      returnObject  = pjp.proceed();
    } catch (Throwable t) {
      throw new AopInvocationException(t.getMessage(), t);
    }
    if (!checkAuthorization(returnObject)) {
      throw new UnauthException();
    }
    return returnObject;
  }
}

这将执行事务内的逻辑。我将实际逻辑移至一个方法,以便 lambda 可以是单个方法而不是代码块。 (个人preference/best实践)。

查看您的代码,您需要更新一些内容:

  1. 如果要调用EntityManager,需要通过以下方式调用,在持久化上下文中使用。

    @PersistenceContext
    private EntityManager em;
    
  2. 为了在事务上下文中进行查询或其他操作,您只需要在方法头上添加 Transactional 标记并且不要从 EntityManager 调用 getTransaction。

    @Transactional
    @Around("AllRestExecPoint()")
    public Object auth(ProceedingJoinPoint point) throws Throwable {
         em.createNativeQuery(query) //If it is necessary. 
         ...
    }