지난번 포스트랑 동일한 맥락이다.
상품을 주문하는데, 주문이 완료되면 회원의 위시리스트에서 해당 상품 옵션을 제거하는 코드다.
@TransactionalEventListener
public void handleOrderCreateEvent(OrderCreateEvent event) {
wishlistRepository.deleteByProductIdAndMemberId(
event.getProduct().getId(),
event.getMember().getId()
);
}
사실 위시리스트에서 지우는건 실패하더라도 주문하기라는 기능에 크게 영향을 미치지 않는다. 그래서 AFTER_COMMIT으로 해당 메서드를 작성해주었다.
실행된 쿼리들을 보면 이 메서드에서 SELECT만 하고 있고, DELETE를 하지 않는다. DELETE를 하기 전에 SELECT를 한다는 것은 알고 있는데, 이게 SELECT는 되고 왜 DELETE가 안되는지 알 수가 없었다.
더더욱 어려웠던건 뭐냐면, 기존에 간단한 CRUD를 작성할 때 까먹고 @Transactional을 붙이지 않아도 정상적으로 DELETE가 작동했었다. 이거 때문에 더 헷갈린 것 같다.
@Transactional(propagation = Propagation.REQUIRES_NEW)를 달아주니까 해결이 되긴 했다.
일단 이전 트랜잭션은 커밋이 된 상태다. 즉, 트랜잭션이 커밋이 된 건 알겠는데 예전에 @Transactional을 달지 않은 간단한 CURD 메서드를 실행했을 땐 왜 성공했고, 지금은 왜 실패하는 건지가 너무 이해하기 힘들었다.
우선, SELECT는 트랜잭션이 생성되지 않아도 커넥션만 있으면 실행이 된다. 그리고 기존 DELETE 메서드도 Repository에 @Transactional이 달려 있어서 트랜잭션이 만들어지고 DELETE를 수행한다고 한다.
또 여기서 생긴 궁금증은 결국 저 메서드 안에서도 repository의 delete 메서드를 호출하는 거니까 @Transactional이 달려있는거 아닌가? 근데 왜 얘는 안되고 쟤는 되냐고? 였다.
결론부터 말하자면 그냥 단순히 @Transcational을 달고 있지 않은 메서드는 새로운 트랜잭션을 열지만, @TranscationalEventListener가 달려있는 메서드는 이미 커밋된 트랜잭션에 참여하기 때문에 이런 차이가 생긴 거였다.
커밋된다고 트랜잭션이 바로 사라지지는 않는다. 그래서 저 코드는 커밋이 된 트랜잭션에 참여만 하지, 커밋되지는 않아서 DB에 반영이 안되는 것이었다.
그래서 명시적으로 트랜잭션을 열어줘야 하는 것이었다. 아니면 BEFORE_COMMIT을 사용해서 커밋 이전에 참여시키면 되지만... 위시리스트에서 삭제하는 부분이 그렇게 중요한 로직은 아니라고 판단해서 그냥 명시적으로 트랜잭션을 열어주는 방법으로 해결을 했다.
며칠동안 이해하기 너무 힘들었다. 사실 AI의 앞뒤 안맞는 설명도 한 몫했지만... 그냥 구글링하고 docs를 보니까 바로 이해가 됐다 😡
영속성 컨텍스트랑 트랜잭션에 대해서는 조금 더 공부를 해봐야겠다. 데이터베이스 강의를 수강하면서 트랜잭션에 관련된 지식은 조금 있었지만 이런 식으로 맞닥뜨리니 머리 아팠고, 영속성 컨텍스트에 대해서 조금만 더 공부를 해봤으면 잘 해결이 될 수 있었을 것 같다.