티스토리 뷰

이전 글에서 이어지는 내용으로 commitOrRollback의 세부 구현을 살펴보자

EventListener 구현체는 JpaEventListener를 구현하고 여기서는 공통 Transaction 작업만 처리해주면 된다

import java.util.function.Consumer;

import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.engine.spi.SharedSessionContractImplementor;

public interface JpaEventListener {

	default void commitOrRollback(
		SharedSessionContractImplementor session,
		Consumer<SharedSessionContractImplementor> task
	) {
		Transaction tx = null;
		try {
			tx = session.beginTransaction();
			task.accept(session);
			tx.commit();
		} catch (RuntimeException ex) {
			if (tx != null) {
				tx.rollback();
			}
			throw ex;
		}
	}
}

 

인자는 session과 task가 있는데 session은 ActionQueue에서 넣어주는 session을 그대로 사용하고

개발자가 넘겨주는 트랜잭션 안에서 수행해야 될 작업을 task로 받는다

SharedSessionContractImplementor는 이전에 설명한 대로 ActionQueue에서 session을 넣어줄 때

Session 구현체로 받기 위해 강제하려 사용한 것이고 여기서도 마찬가지로 추상화된 형태로 받아온다

이전 글에서 task로 넘겨주는 부분은 다음과 같다

session -> pandaRepository.deleteByBearId(bear.getId())

 

session을 넘기기는 하지만 session을 이용하지는 않는다, 따라서 인자가 굳이 Consumer일 필요는 없다

다음을 참고해 FunctionalInterface를 사용하던가 직접 void -> void 형태의 인터페이스를 만들어 사용해도 된다

1. Consumer -> session을 이용하고 반환 값이 필요 없는 경우

2. Supplier -> session을 이용하지 않고 반환 값이 필요한 경우

3. Function -> session을 이용하고 반환 값이 필요한 경우 

4. Runnable -> session을 이용하지 않고 반환 값이 필요 없는 경우

 

Hibernate의 기본 예외 처리를 따르려 catch block에서 Exception으로 잡지 않고 RuntimeException으로 잡았다

Transaction 안에서 Checked Exception이 발생한 경우에는 롤백시키지 않는다

이 부분도 Hibernate는 왜 그런 선택을 했는지 정리를 해봐야겠다, 우선은 간단하게 Transcational 애노테이션을 살펴보자

Transactional 애노테이션에서 다음과 같은 옵션들을 이용해 예외 설정을 달리할 수 있다

 

org.springframework.transaction.annotation.Transactional

 

다음으로 직접 만든 CustomListener를 등록해야 한다

Integrator 인터페이스를 구현한 클래스를 생성하고 integrate, disintegrate를 재정의해주면 된다

Hibernate에 직접 만든 리스너를 통합하기 위해 구현해주어야 하는 계약 인터페이스로 초기화할 때

integrate가 호출돼 리스너들이 등록되고, 애플리케이션이 내려갈 때 콜백으로 disintegrate가 호출된다

import org.hibernate.boot.Metadata;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.event.service.spi.EventListenerRegistry;
import org.hibernate.event.spi.EventType;
import org.hibernate.integrator.spi.Integrator;
import org.hibernate.service.spi.SessionFactoryServiceRegistry;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import com.springanything.jpa.JpaEventListenerConfig;

public class EventListenerIntegrator implements Integrator {

	@Override
	public void integrate(
		Metadata metadata,
		SessionFactoryImplementor sessionFactory,
		SessionFactoryServiceRegistry serviceRegistry
	) {
		EventListenerRegistry eventListenerRegistry =
			serviceRegistry.getService(EventListenerRegistry.class);

		eventListenerRegistry.getEventListenerGroup(EventType.POST_DELETE)
			.appendListener(new BearListener());
	}

	@Override
	public void disintegrate(
		SessionFactoryImplementor sessionFactory,
		SessionFactoryServiceRegistry serviceRegistry
	) {
	}
}

 

EventListenerIntegrator는 Service Provider Interface, (SPI)로

애플리케이션이 띄워지는 시점에 ServiceLoader에 의해 읽히고 설정된다

ServiceLoader를 한번 파봐야 할 것 같다만 내용이 너무 방대해 여기서는 간단하게 살펴보자

main/resources/META-INF/services에 Provider (서비스 구현체 ex.Hibernate) 들을 위한 설정 파일이 들어간다

org.hibernate.integrator.spi.Integrator라는 이름으로 파일을 만들고 직접 만든 구현체의 위치를 넣어준다

 

 

여기까지 모든 설정을 마쳤다면 테스트를 해보면 된다

보기 좋게 p6spy 설정을 해놓고 실제로 삭제가 잘 되는지 브라우저 테스트를 진행해봤다

 

 

이 글에서는 POST_DELETE만 다뤘지만 hibernate 라이브러리를 넣어둔 후,

EventType 클래스를 찾아보면 다양한 LifeCycle에 대해 다룰 수 있다

EventListenerIntegrator에 원하는 LifeCycle에 EventType에 맞는 인터페이스 구현한 리스너를 등록해주면 된다

 

org.hibernate.event.spi.EventType

댓글
링크
글 보관함
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
Total
Today
Yesterday