TransactionalEventListener 데이터 변경이 안돼요

김민창·2024년 9월 8일
0

trouble shooting

목록 보기
11/11
post-thumbnail
post-custom-banner

TransactionalEventListener 가 동작을안한다!!!

그러니깐 내가 원하는대로 동작을 하지않는다. 이벤트 리스너에서 데이터 변경이 반영되지 않는다...

간단한 코드를 보겠다.


  • GroupService.kt 에서 Group 을 삭제하고 삭제 이벤트를 발행한다.
	/**
    * GroupService.kt
    */
    @Transactional
    fun deleteGroupPublish(id: Long){
        groupRepository.deleteById(id)
        applicationEventPublisher.publishEvent(
                DeleteGroupEvent(id)
        )
    }

  • 발행한 이벤트를 받아서 Group 에 속하는 GroupMember 를 삭제한다.
	/**
    * GroupMemberService.kt
    */
    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    fun deleteGroupMemberEventHandler(event: DeleteGroupEvent){
        groupMemberRepository.deleteByGroupId(event.id)
    }

  • 그에따른 간단한 테스트를 다음처럼 구성해봤다.
@SpringBootTest
class GroupServiceTest{

    @Autowired
    lateinit var groupService: GroupService
    @Autowired
    lateinit var groupRepository: GroupRepository
    @Autowired
    lateinit var groupMemberRepository: GroupMemberRepository

    @Test
    fun delete_group_and_publish_event(){
        // given
        val group = groupRepository.save(Group())
        val groupMember = groupMemberRepository.save(GroupMember(group.no))
        // when
        groupService.deleteGroup(group.no)
        // then
        val groupList = groupRepository.findAll()
        val groupMemberList = groupMemberRepository.findAll()
        assertThat(groupList).isEmpty()
        assertThat(groupMemberList).isEmpty()
    }
}

  • 왜 실패하는거야

이벤트 문제가 아니고 Transaction를 보자

그러니깐 ORM 에 등록해서 데이터베이스가 실제로 변경되게 하려면 commit, rollback 등의 기능이 되어야하고, 그걸 편하게 하기위해서 TransactionManager 인터페이스가 존재하고, 이걸 대부분 구현해놓은 AbstractPlatformTransactionManager 추상클래스가 존재하고, 이걸 또 각자 사용하는 ORM에서 TransactionManager 를 구현해놓았는데...
나중에 블로그를 따로 정리하겠다.

구글링으로 좀 이것저것 찾아봤었는데 여기 블로그를 좀 참고했다.

어디서 커밋을 하나요

일단 내부구조를 슥 한번 보겠다.

변경사항이 반영되는doCommit() 을 실행하는 부분은 여기 한군데 밖에 없다

AbstractPlatformTransactionManager 클래스의 processCommit() 메서드이다.

코드를 보아 status.isNewTransaction() 이 true 여야 doCommit() 이 실행되어 데이터베이스 변경이 반영할수있다는걸 알수 있다.


언제 커밋을 하나요

그렇다면 status.isNewTransactiion() 의 상태값이 세팅 부분을 찾아보겠다.

동일한 클래스의 getTransaction() 에서 현재 실행중인 Transaction 이 새로운 트랜잭션인지 분기한다는걸 확인했다.

현재 Transactional 의 propagation이 기본값 (Propagation.REQUIRED)으로 되어있으니 하단의 조건문에 걸려 트랜잭션을 시작하게 된다.


그리고 해당 메서드는 트랜잭션을 생성하며 newTransaction이 true로 들어가는걸 확인할수 있다.


AbstractPlatformTransactionManager 클래스의 processCommit() 메서드의 status.isNewTransaction()이 true 로 들어갈수가 있겠다.
(중복된 사진)


그래서 리스너에서는 외않되

동일하게 getTransaction()으로 현재 트랜잭션의 상태값을 받아오는건 동일하지만, 상위 클래스에서 전파된 트랜잭션이 존재하기 때문에 handleExistingTransaction() 메서드를 타게 된다.

그리고 handleExistingTransaction 내부를 확인하면 definition.getPropagationBehavior() 에 따라서 트랜잭션의 newTransaction 의 상태값이 다르게 들어가는걸 확인할수 있다.

그리고 해당 값의 기본값은 Transactional의 기본값인 REQUIRED

PROPAGATION_REQUIRED 상태값을 따라가면 마지막에 newTransaction 이 false 로 생성되게 되고 아까 확인했었던 commit이 일어나지 않게 된다.


그럼 어케해!!!

상위클래스에서 전파된 트랜잭션은뭐 그거대로 두고 새로운 트랜잭션을 열면 된다.

아까 확인했었던 handleExistingTransaction 에서 트랜잭션 PROPAGATIONREQUIRES_NEW 라면 최초에 트랜잭션을 열때와 동일하게 여는 방식을 사용하는걸 볼수 있다.

그렇지만 여기 블로그 도 한번 확인해보도록 하자.

profile
개발자 팡이
post-custom-banner

0개의 댓글