본문 바로가기
Coding/Java Spring

Spring Hibernate EventListener로 엔티티 변경 감지하는 방법

by Hide­ 2022. 1. 21.
반응형

개요

현재 토이 프로젝트에 간단하게 CQRS 아키텍처를 도입해보고 있다. 따라서 쓰기DB, 읽기DB 총 2개의 데이터베이스가 존재하고 쓰기 요청이 오는 경우 쓰기DB에 데이터를 기록함과 동시에 SQS에 이벤트를 발송하고 있다. 그리고 읽기DB에서는 해당 SQS를 컨슈밍하고 있다가 변경 이벤트가 들어오면 내부적으로 동기화를 진행하는 형태이다.

위 요구사항에 따라 엔티티에 변경사항이 생긴다면 감지한 후 이벤트를 발생해줘야하기 때문에 관련 이벤트 리스너를 찾아봤는데, Hibernate의 EventListener를 활용하면 손쉽게 가능하다는것을 알게 되었다. JPA에서 이벤트 리스너를 제공해주지만 Hibernate의 기능을 사용해야 좀 더 세분화하여 활용할 수 있다고 하여 사용하게 됐다. (참고: https://www.youtube.com/watch?v=fg5xbs59Lro) 

PostInsertEventListener

Insert쿼리가 나간 후 발생되는 리스너이다. 

@Component
@RequiredArgsConstructor
@Slf4j
public class CustomPostInsterEventListener implements PostInsertEventListener {
    @Override
    public void onPostInsert(PostInsertEvent event) {
        log.info("Emit user insert event");
        publishRelatedEntity(event.getEntity());
    }

    @Override
    public boolean requiresPostCommitHanding(EntityPersister persister) {
        return false;
    }

    private void publishRelatedEntity(Object entity) {
        try {
            publishEventForUser(entity);
        } catch (Exception e) {
            log.error(String.valueOf(e));
        }
    }

    private void publishEventForUser(Object entity) throws JsonProcessingException {
        if (!(entity instanceof User)) {
            return;
        }
        ...
    }
}

onPostInsert()requiresPostCommitHaning() 총 2개의 메소드를 오버라이드 해야한다. onPostInsert() 쪽에 이벤트가 들어오는데 getEntity() 메소드를 통해 어떠한 엔티티가 변경되었는지 파악할 수 있다. publishRelatedEntity(), publishEventForEvent()는 나중에 여러개의 엔티티가 들어왔을때 해당 엔티티에 맞게 작업을 처리해주기 위해 작성한 코드이다.

PostUpdateEventListener

Update쿼리가 나간 후 발생되는 리스너이다.

@Component
@RequiredArgsConstructor
@Slf4j
public class CustomPostUpdateEventListener implements PostUpdateEventListener {
    @Override
    public void onPostUpdate(PostUpdateEvent event) {
        log.info("Emit user update event");
        publishRelatedEntity(event.getEntity());
    }

    @Override
    public boolean requiresPostCommitHanding(EntityPersister persister) {
        return false;
    }
    ...
}

onPostUpdate()requiresPostCommitHanding() 총 2개의 메소드를 오버라이드 해야한다. Insert와 같이 onPostUpdate에 이벤트가 들어온다.

PostDeleteEventListener

Delete쿼리가 나간 후 발생되는 리스너이다.

@Component
@RequiredArgsConstructor
@Slf4j
public class CustomPostDeleteEventListener implements PostDeleteEventListener {
    @Override
    public void onPostDelete(PostDeleteEvent event) {
        log.info("Emit user delete event");
        publishRelatedEntity(event.getEntity());
    }

    @Override
    public boolean requiresPostCommitHanding(EntityPersister persister) {
        return false;
    }
    ...
}

onPostDelete()requiredPostCommitHanding() 총 2개의 메소드를 오버라이드 해야한다. 삭제 또한 마찬가지로 onPostDelete에 이벤트가 들어온다.

EntityManagerFactory

이제 위에서 생성해준 리스너를 실제로 이벤트 리스너에 등록해줘야 한다.

@Component
@RequiredArgsConstructor
@Slf4j
public class HibernateListener {
    private final EntityManagerFactory entityManagerFactory;
    private final CustomPostInsterEventListener customPostInsterEventListener;
    private final CustomPostUpdateEventListener customPostUpdateEventListener;
    private final CustomPostDeleteEventListener customPostDeleteEventListener;

    @PostConstruct
    private void init() {
        log.info("Initializing HibernateListener");
        SessionFactoryImpl sessionFactory = entityManagerFactory.unwrap(SessionFactoryImpl.class);
        EventListenerRegistry registry = sessionFactory.getServiceRegistry().getService(EventListenerRegistry.class);
        registry.getEventListenerGroup(EventType.POST_INSERT).appendListener(customPostInsterEventListener);
        registry.getEventListenerGroup(EventType.POST_UPDATE).appendListener(customPostUpdateEventListener);
        registry.getEventListenerGroup(EventType.POST_DELETE).appendListener(customPostDeleteEventListener);
    }
}

코드를 보면 알겠지만 getEventListenerGroup에서 EventType별로 특정 동작 시 어떠한 이벤트 리스너를 등록할 지 정해줄 수 있다. 위에서 우리가 생성한 리스너를 appendListener() 메소드를 통해 등록해주면 정상적으로 동작하는 모습을 확인할 수 있다.

참고로 본 포스팅에서는 Post~ 리스너들만 다뤘지만 Pre~, PostCommit~ 등 여러가지의 이벤트 리스너들이 존재하므로 각 요구사항에 맞게 활용하면 된다.