반응형

ApplicationEventPublisher를 사용하다 보면 EventListenerTransactionalEventListener을 사용하게 되는데 이는 어떻게 다른것인지 확인해 봅니다


간단하게 Member 저장시 이벤트를 발생시켜서 어떻게 다른지 코드를 통해 확인해 봅시다.

아래의 코드는 멤버 가입을 하는 서비스이며, 멤버를 저장하게 되면서 MemberSave라는 이벤트 객체를 만들어서 eventPublisher를 통해 이벤트를 발행하게 됩니다.

이때 Step 1 이라는 로그를 찍고 다음에 이벤트발행, 이후 Step 2, Step 3를 진행하게 되며 마지막에 MemberDto의 이름이 "오류"라면 RuntimeException 예외를 던지게 되어 롤백되게 처리해놨습니다

 

data class MemberSave(
    val id : Int = 0,
    val name : String
)

@Service
class MemberRegisterService(
    private val eventPublisher: ApplicationEventPublisher
) {
    @Transactional
    fun signup(memberDto : MemberDto){
        val saveEvent : MemberSave = Member(memberDto).saveEvent()
        logger.info("step 1")
        eventPublisher.publishEvent(saveEvent)
        logger.info("step 2")
        logger.info("step 3")

        if (memberDto.name == "오류") throw RuntimeException("에러")
    }
}

다음은 이벤트을 구독하는 리스너 입니다

@Component
class MemberEventHandler {

    @EventListener
    fun defaultEventListener(event : MemberSave){
        logger.info("defaultEventListener ---> {}", event)
    }

    @TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT)
    fun transactionalEventListenerBeforeCommit(event : MemberSave){
        logger.info("TransactionPhase.BEFORE_COMMIT ---> {}", event)
    }

    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    fun transactionalEventListenerAfterCommit(event : MemberSave){
        logger.info("TransactionPhase.AFTER_COMMIT ---> {}", event)
    }

    @TransactionalEventListener(phase = TransactionPhase.AFTER_ROLLBACK)
    fun transactionalEventListenerAfterRollback(event : MemberSave){
        logger.info("TransactionPhase.AFTER_ROLLBACK ---> {}", event)
    }

    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMPLETION)
    fun transactionalEventListenerAfterCompletion(event : MemberSave){
        logger.info("TransactionPhase.AFTER_COMPLETION ---> {}", event)
    }

}

일반적인 @EventListener와 트랜잭션에 관련된 @TransactionalEventListener를 정의해놨습니다.

여기서 @TransactionalEventListener는 phase를 통해 언제 실행될지 지정할 수 있습니다

TransactionPhase 설명
BEFORE_COMMIT 트랜잭션 커밋 직전에 실행됩니다
AFTER_COMMIT 트랜잭션 커밋 이후에 실행됩니다 (기본값)
AFTER_ROLLBACK 트랜잭션 커밋 롤백 이후에 실행됩니다
AFTER_COMPLETION 트랜잭션이 끝난 이후에 실행됩니다

이제 테스트코드를 작성해서 눈으로 확인해 보겠습니다. 말이 테스트코드지 별로 쓴건 없네요 ㅎㅎ

@SpringBootTest
class EventApplicationTest{

    @Autowired
    lateinit var memberRegisterService: MemberRegisterService

    @Test
    fun successEvent(){
        memberRegisterService.signup(MemberDto("라이언"))
    }

    @Test
    fun failEvent(){
        assertThrows<RuntimeException> {
            memberRegisterService.signup(MemberDto("오류"))
        }
    }
}

일반적인 성공 테스트와, RuntimeException 예외를 던지는 두개의 테스트를 만들었습니다.

먼저 성공 테스트를 실행해보면 다음과 같은 결과가 나옵니다

c.b.a.MemberRegisterService    : step 1
c.b.a.event.MemberEventHandler : defaultEventListener ---> MemberSave(id=38, name=라이언)
c.b.a.MemberRegisterService    : step 2
c.b.a.MemberRegisterService    : step 3
c.b.a.event.MemberEventHandler : TransactionPhase.BEFORE_COMMIT ---> MemberSave(id=38, name=라이언)
c.b.a.event.MemberEventHandler : TransactionPhase.AFTER_COMMIT ---> MemberSave(id=38, name=라이언)
c.b.a.event.MemberEventHandler : TransactionPhase.AFTER_COMPLETION ---> MemberSave(id=38, name=라이언)

그리고 예외를 발생해서 롤백이 되는 테스트를 돌리면 다음과 같은 결과가 나옵니다

c.b.a.MemberRegisterService    : step 1
c.b.a.event.MemberEventHandler : defaultEventListener ---> MemberSave(id=66, name=오류)
c.b.a.MemberRegisterService    : step 2
c.b.a.MemberRegisterService    : step 3
c.b.a.event.MemberEventHandler : TransactionPhase.AFTER_ROLLBACK ---> MemberSave(id=66, name=오류)
c.b.a.event.MemberEventHandler : TransactionPhase.AFTER_COMPLETION ---> MemberSave(id=66, name=오류)

두 개의 결과에 확인할 수 있는 것은, @EventListener는 호출 시점에 바로 실행된다는 점입니다. 이후 결과에 상관없이 호출되는 시점에 바로 진행되는 것을 볼 수 있습니다.

@TransactionalEventListener는 먼저 트랜잭션이 커밋이나 롤백이 되는 시점에 이벤트가 실행된다는점입니다. 앞서 Step 1 이후에 바로 이벤트를 발행하였는데 Step 2, 3을 지나 실행되는 것을 볼 수 있습니다. 그리고 커밋이나 롤백에 따라 출력되는 로그가 다르게 나오는데, 커밋되는 상황에서는 BEFORE_COMMIT, AFTER_COMMIT, AFTER_COMPLETION 순으로 실행이 됩니다.  

* 커밋되는 시나리오의 이벤트 발생순서
BEFORE_COMMIT

[트랜잭션 커밋]
AFTER_COMMIT
AFTER_COMPLETION

롤백되는 상황에서는 AFTER_ROLLBACK, AFTER_COMPLETION 순으로 실행됩니다

* 롤백되는 시나리오의 이벤트 발생순서
[트랜잭션 롤백]
AFTER_ROLLBACK
AFTER_COMPLETION

상황에 따라 @EventListener, @TransactionalEventListener을 사용하면 될것 같습니다.

반응형

'개발 > Kotlin' 카테고리의 다른 글

Kotlin - Enum 사용  (0) 2022.07.22
Kotlin DSL로 Gradle 버전관리하기  (0) 2022.07.21
Gradle - dependency-management Plugin  (0) 2022.06.19
QueryDSL 설정 - Kotlin  (0) 2022.06.19
Kotlin Basic - 함수와 변수  (0) 2022.06.18

+ Recent posts