JPA
Jpa Entity Listener
Bong Gu
2021. 11. 25. 02:53
728x90
JPA
JpaEntityListener
Event
생성방법
ApplicationEvent 상속
@Getter public class MyEvent extends ApplicationEvent { private int data; public MyEvent(object source) { super(source); } public MyEvent(object source, int data) { super(source); this.data = data; } }
Spring 4.2 부터는 ApplicationEvent를 상속받지 않아도 이벤트로 사용할 수 있다.
@Getter public class MyEvent { private int data; public MyEvent(int data) { this.data = data; } }
발행방법
ApplicationEventPublisher 사용
@RequiredArgsConstructor public class MyService { private final ApplicationEventPublisher eventPublisher; public void publishEvent() { eventPublisher.publishEvent(new MyEvent(100)); } }
ApplicationContext 사용
@RequiredArgsConstructor public class MyService { private final ApplicationContext ctx; public void publishEvent() { ctx.publishEvent(new MyEvent(100)); } }
Spring이 제공하는 Event
- ContextRefreshEvent : ApplicationContext를 초기화하거나, 리프레쉬하는 경우 발생
- ContextStartedEvent : ApplicationContext를 start하여 라이프사이클 Bean들이 시작신호를 받은 시점에 발생
- ContextStoppedEvent : ApplicationContext를 stop하여 라이프사이클 Bean들이 정지 신호를 받은 시점에 발생
- ContextCloseEvent : ApplicationContext를 close하여 싱글톤 빈들이 소멸되는 시점에 발생
- RequestHandlerEvent : HTTP 요청을 처리했을 때, 발생
EventHandling
ApplicationListener 구현
@Service public class MyListener implements ApplicationListener<MyEvent> { @Override public void execute(MyEvent event) { System.out.println("Event : " + event.getData()); } }
Spring 4.2 부터는 ApplicationEvent를 상속하지 않아도되기 때문에, ApplicationListener를 구현하지 않고 어노테이션을 사용하여 Listener를 등록
@Service public class MyListener { @Async @Order(Ordered.HIGHEST_PRECEDENCE) @EventListener public void execute(MyEvent event) { System.out.println("Event : " + event.getData()); } }
@Order
를 사용하여 Listener 순서를 지정해 줄 수 있다.
@TransactionalEventListener
- Spring 4.2 부터 사용 가능
- Spring Transactio 상태에 따라 발생하는 이벤트를 처리해주는 이벤트 리스너
@EventListener
는 트랜잭션 범위내에서 동기적으로 실행되고,@TransactionalEventListener
는 커밋 전/후 등을 자유롭게 지정할 수 있다.- 트랜잭션이 없다면 장독하지 않는다. 단,
fallbackExecution
이 설정되어있으면 작동한다.
- 트랜잭션이 없다면 장독하지 않는다. 단,
@Async
를 사용하여 비동기로 이벤트를 처리할 수 있다.@Async
를 걸지 않으면 이벤트처리를 마칠때 까지, DB커넥션을 놓지 않는다.
- 설정
- AFTER_COMMIT (default setting) : 커밋후에 동작
- Entity를 수정해도 처리되지 않는다. 이미 커밋했기 때문에
- 데이터 변경작업이 필요하다면
@Transactional(Propagtion.REQUIRES_NEW)
를 설정해주자.- 이벤트 작업의 성공 /실패 여부가 영향끼지 않도록 REQUIRES_NEW 전략 사용
- AFTER_ROLLBACK : 롤백후에 동작
- AFTER_COMPLETION : 트랜잭션이 끝난후에 동작 (커밋 / 롤백 여부에 상관없이)
- BEFORE_COMMIT : 커밋전에 동작
@Async
를 사용하지 말자.
- AFTER_COMMIT (default setting) : 커밋후에 동작
EntityListeners
JPA Entity에서 이벤트가 발생할 때마다 콜백 메서드를 실행 시킬 수 있다.
@Entity public class Entity { @Id @GeneratedValue private Long id; private int data; @PrePersist public void prePersist() { System.out.println("Pre Persist") } }
콜백 메서드
>>> prePersist Hibernate: call next value for hibernate_sequence Hibernate: insert into user (created_at, email, gender, name, updated_at, id) values (?, ?, ?, ?, ?, ?) >>> postPersist Hibernate: select user0_.id as id1_1_0_, user0_.created_at as created_2_1_0_, user0_.email as email3_1_0_, user0_.gender as gender4_1_0_, user0_.name as name5_1_0_, user0_.updated_at as updated_6_1_0_ from user user0_ where user0_.id=? >>> postLoad >>> preUpdate Hibernate: update user set email=?, gender=?, name=?, updated_at=? where id=? >>> postUpdate >>> preRemove Hibernate: delete from user where id=? >>> postRemove
@PrePersist
: insert@PreRemove
: delete@PostPersist
@PostRemove
@PreUpdate
: merge@PostUpdate
@PostLoad
: select 조회가 된 직후- PreUpdate는 실질적으로 UpdateSQL문이 실행되었을때, 실행이된다.
즉, flush 또는 트랜잭션 종료시점
이미변경된 값으로 조회가 도ㅚ어 결과정적으로 PostUpdate와 동일한 결과를 보일 수 있다.@Transient
필드를을 사용해서, 이전값들을 알 수 있다.
특정 EntityListener를 사용하는 방법
@EntityListeners({AuditingEntityListener.class, MyListener.class}) @Entity public class Entity { @Id @GeneratedValue private Long id; private int data; }
- 여러 Entity에서 공통 Listener를 사용할 수 도 있다.
- 여러 Listener도 등록 가능하다.
EntityListeners DI 방법
Spring Bean 생성시점이 달라서 DI가 잘 안된다.
EntityManagerFactory를 Bean으로 등록할 때, EntityListener에 대해서 Bean으로 등록하는 작업이 존재한다.
따라서 EntityListener에서 EntityManagerFactory를 사용하는 Repository류의 Bean을 주입하면 문제가 발생한다.
우회 방법
ApplicationContext
public class MyListener { @Autowired private ApplicationCOntext ctx; @PrePersist public void prePersist(Entity entity) { EntityRepository repo = ctx.getBean(EntityRepository.class); } }
- EntityListeners에 등록 시, 기본생성자가 필요하기 때문에 필드주입장식을 사용한다.
@Lazy
public class MyListener { @Lazy @Autowired private EntityRepository repository @PrePersist public void prePersist(Entity entity) { } }
- context refresh 시점에는 proxy 였다가, 사용 시 초기화
BootstrapMode Deferred or Lazy
@EnableJpaRepositories(bootstrapMode = BootstrapMode.DEFERRED)
spring: data: jpa: repositories: bootstrap-mode: deferred
- BootstrapMode를 Deffrred로 설정하게 되면, JpaRepositories를 proxy로 생성 해준다.
- 또한, Spring context가 load하는 thread와 다른 thread를 이용해서 작업이 진행되고,
ContextRefreshedEvent
에 trigger에 의해서 repository가 초기화가 진행된다. - 결론은
@Lazy
와 비슷하게 동작 하지만 application이 시작 전에Repository
들이 초기화가 보장되어 있고, load 속도도 빨라진다. - BootstrapMode를 변경하는 방법은
@EnableJpaRepositories
과, properties를 이용해서 설정 가능하다. - Lazy의 경우는 앞에 설명한 방식을 전체
Repository
에 일괄 적용 해주게 된다. - Lazy로 Application을 시작하는 경우 런타임시에 문제가 발생할 수 있으니 주의해야 한다.
우회 방법들이 좋아 보이지는 않아 다른방법을 찾아보던중, 다른 event를 발행하여 처리하는 방법을 찾았다.
EntityListeners 등록
@EntityListeners(MyListener.class) @Entity public class Entity { @Id @GeneratedValue private Long id; private int data; }
다른 이벤트를 발행
@Getter public class TheOtherEvent { private Entity entity; public TheOtherEvent(Entity entity) { this.entity = entity; } }
public class MyListener { @Autowired private ApplicationEventPublisher eventPublisher; @PrePersist public void prePersist(Entity entity) { eventPublisher.publishEvent(new TheOtherEvent(entity)); eventpublisher.publishEvent(new SomethingEvent(entity)); } }
@RequiredArgsConstructor @Service public class TheOtherEventListener { private final EntityRepository repository @EntityListener public void prePersist(TheOtherEvent event) { Entity entity = event.getEntity(); repository.save(entity); } }
- ApplicationEventPublisher가 DI 되는 이유는 사용자가 생성한 빈이 아닌, 스프링에서 제공하는 이미 등록된 빈으로 MyListener 생성보다 순서가 빠르다.
참고
- https://kwonnam.pe.kr/wiki/springframework/transaction/transactional_event_listener
- https://sukyology.tistory.com/18
- https://kangwoojin.github.io/programing/jpa-entity-listeners/
- https://kimchanjung.github.io/programming/2020/06/28/spring-jpa-antity-listner-autowired-not-working/
- https://milenote.tistory.com/79
728x90