TransactionalEventListener

지능바바·2023년 5월 27일
0

우리가 어떤 작업을 하다보면 트랜잭션이 종료 된 이후에 뭔가를 해야 할 필요가 있다.
예들들면 캐시를 삭제하는 작업이 커밋 이후에 진행되어야 하는 작업이다. 왜냐하면 트랜잭션 내에서 데이터의 변경사항이 아직 커밋되지 않았을때 캐시를 삭제하게 되면 캐시삭제와 커밋 그 사이에서 다시 이전의 변경전 데이터로 캐시가 생성될 수 있기 때문이다.
그래서 우리는 아래와 같은 순서로 비즈니스를 작성할 필요가 있다.

트랜잭션 시작 -> 비즈니스 로직 -> 커밋 -> 캐시삭제

이때 캐시를 삭제하기 위해서 우리는 캐시 삭제에 필요한 값을 넘겨야 할 필요가 있다. 그 값이 비즈니스로직이 실행되는 메소드의 리턴값과 동일하거나 리턴값에 원래 포함되어 있다면 문제가 없지만 그렇지 않은경우 불필요하게 리턴값에 캐시 삭제를 위한 값을 추가해야 한다. 물론 이 외에도 ThreadLocal을 사용하는 것도 하나의 방법이 될 수 있겠지만 번거롭다.
이럴때 TransactionalEventListener를 사용하면 문제를 쉽게 해결할 수 있다.

1. TransactionalEventListener란?

스프링에서 제공해주는 EventListener들 중에서 Transactional과 관련된 이벤트를 받아서 처리해 주는 EventListener이다.
TransactionalEventListener 는 다음의 타이밍에 이벤트를 실행할 수 있도록 설정할 수 있다.

AFTER_COMMIT (Default) : 커밋이 된 이후에 이벤트가 실행된다.
AFTER_ROLLBACK : 롤백이 된 이후에 이벤트가 실행된다.
AFTER_COMPLETION : 트랜잭션이 마무리(commit or rollback) 된 이후에 실행된다.
BEFORE_COMMIT : 트랜잭션이 커밋되기 전에 이벤트가 실행된다.

2. 사용법

아래의 코드에서와 같이 ApplicationEventPublisher를 이용해 이벤트를 발행한다.

public record CategoryEvent(long categoryNo) { }


public class CategoryService {
  private final ApplicationEventPublisher applicationEventPublisher;

  @Transactional
  public void delete(long categoryNo) {
      categoryRepository.deleteById(categoryNo);
      // 이벤트 발행.
      applicationEventPublisher.publishEvent(new CategoryEvent(categoryNo));
  }
}

그리고 발행된 이벤트는 아래와 같이 실행시킬 수 있다.

@Component
public class CategoryEventListener {

    @TransactionalEventListener
    public void handle(CategoryEvent event) {
        System.out.println(event.categoryNo());
    }
}

@TransactionalEventListener 어노테이션을 붙여줌으로서 핸들러를 설정해 주고, applicationEventPublisher.publishEvent 를 통해 이벤트를 발행할 때 넘겨준 객체를 파라미터로 설정해 줌으로서 어떤 이벤트에 대한 핸들러인지 알 수 있다.

3. 기타 유의사항

@TransactionalEventListener 를 사용할 때 유의해야 할 사항이 몇가지 있다.

1. TransactionalEventListener는 비동기로 실행되는 기능이 아니다.
물론 @Async 어노테이션을 핸들러에 붙여줌으로서 비동기 실행이 가능하지만 어디까지나 기본은 요청스레드와 동일한 스레드로 동작한다는 것이다. 그렇기 때문에 핸들러에서 예외가 발생할 경우 해당 리퀘스트 자체가 예외가 발생된채로 응답된다.

2. DB커넥션이 유지된다.
핸들러에서 사용되는 DB커넥션은 이벤트를 발행한 스레드와 동일한 커넥션을 그대로 사용한다.
즉, 이벤트가 종료될 때 까지 DB커넥션이 반환되지 않는다.
그러므로 만약 핸들러에 아래와 같이 트랜잭션을 설정하면 커넥션이 부족해서 오류가 발생할 수도 있다.

@Transactional(propagation = Propagation.REQUIRES_NEW) 

이전의 커넥션을 아직 Connection pool에 반환하지 않은상태에서 새로운 커넥션을 요청했으니 Connection pool에 대기중인 커넥션이 없다면 계속 커넥션을 획득하기 위해 대기하게 될 수 있기 때문이다.

0개의 댓글