Как создать транзакцию в функции Spring Boot aop @Around? - PullRequest
0 голосов
/ 23 января 2019

Я хочу реализовать метод авторизации с помощью Spring Boot AOP. Исходная идея заключается в том, что если возвращаемый объект, возвращаемый из вызовов REST, не прошел проверку авторизации, он выдаст несанкционированное исключение.

Я делаю это так:

@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 выполнит некоторую INSERT или UPDATE для моей БД, она выполнится перед проверкой моей авторизации. Таким образом, UnauthException будет выброшено, но транзакция все еще фиксируется.

Первая попытка вручную создать транзакцию перед вызовом proceed() и зафиксировать ее до возврата, но она не удалась.

@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 для настройки.

Ответы [ 2 ]

0 голосов
/ 23 января 2019

Глядя на свой код, нужно обновить пару вещей:

  1. Если вы хотите вызвать EntityManager, вам нужно позвонить следующим образом, используя в контексте постоянства.

    @PersistenceContext
    private EntityManager em;
    
  2. Чтобы выполнить запросы или что-то в контексте транзакции, вам просто нужно добавить тег Transactional в заголовок метода и не вызывать getTransaction из EntityManager.

    @Transactional
    @Around("AllRestExecPoint()")
    public Object auth(ProceedingJoinPoint point) throws Throwable {
         em.createNativeQuery(query) //If it is necessary. 
         ...
    } 
    
0 голосов
/ 23 января 2019

Судя по вашим тегам, вы используете Spring Boot. 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;
  }
}

Это выполнит логику внутри транзакции. Я переместил реальную логику в метод, чтобы лямбда-код мог быть одним методом вместо блока кода. (Личные предпочтения / лучшие практики).

...