JPA기초 Entity Event Hooking
Entity Event Hooking
JPA 를 사용한다는 것은 DB CRUD 를 JPA 를 통해 수행한다는 의미이다.
그러므로 JPA 에서는 다양한 이벤트에 대해서 CallBack 을 받을 수 있도록 하는 여러 방법을 제공하고 있다.
이러한 이벤트는 CRUD 가 일어나기 전, 후에 각각 이벤트를 통지 받을 수 있으며 여러가지 방법을 제공하고 있다.
Entity Event
- @PrePersist: 새로운 엔터티가 저장되기 전에 호출된다.
- @PostPersist: 새로운 엔터티가 저장되고 난 후에 호출 된다.
- @PreRemove: 엔터티가 삭제 되기 전에 호출된다.
- @PostRemove: 엔터티가 삭제 되고 난 후에 호출된다.
- @PreUpdate: 엔터티가 업데이트 되기 전에 호출된다.
- @PostUpdate: 엔터티가 업데이트 되고난 후에 호출된다.
- @PostLoad: 엔터티가 로드된 후에 호출된다.
위 엔터티에 대한 이벤트를 확인해 보면, 저장전/후, 삭제전/후, 업데이트전/후, 조회후 이렇게 호출된다는 의미이다.
각 상황에 따라서 이벤트의 속성값을 조회하면, 엔터티가 DB에 오퍼레이션이 발생하는 순간 엔터티의 값들을 조회하고, 필요한 작업을 수행할 수 있다.
보통 이러한 작업을 후킹이라고 부른다.
Entity에 Event 걸어주기.
User.java 에 다음과 같은 코드를 추가해보자.
@PrePersist
public void callBackPrePersist() {
log.info("fireEvent PrePersist: ");
}
@PostPersist
public void callBackPostPersist() {
log.info("fireEvent PostPersist: ");
}
@PreRemove
public void callBackPreRemove() {
log.info("fireEvent PreRemove");
}
@PostRemove
public void callBackPostRemove() {
log.info("fireEvent PostRemove");
}
@PreUpdate
public void callBackPreUpdate() {
log.info("fireEvent PreUpdate");
}
@PostUpdate
public void callBackPostUpdate() {
log.info("fireEvent PostUpdate");
}
@PostLoad
public void callBackPostLoad() {
log.info("fireEvent PostLoad");
}
위 상황과 같이 User 엔터티 객체를 DB 에 저장, 수정, 조회, 삭제를 수행할때 어떠한 동작이 수행되는지 알 수 있다.
2020-11-10 22:30:41.725 INFO 3851 --- [nio-8080-exec-7] c.s.practical.simpleboard.entity.User : fireEvent PrePersist:
Hibernate:
select
userdetail0_.id as id1_7_0_,
userdetail0_.avatar_img as avatar_i2_7_0_,
userdetail0_.category as category3_7_0_,
userdetail0_.joined_at as joined_a4_7_0_,
userdetail0_.modified_at as modified5_7_0_,
userdetail0_.nick as nick6_7_0_
from
user_detail userdetail0_
where
userdetail0_.id=?
2020-11-10 22:30:41.726 TRACE 3851 --- [nio-8080-exec-7] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [kido]
Hibernate:
insert
into
user
(birth, created_at, name, id)
values
(?, ?, ?, ?)
2020-11-10 22:30:41.729 TRACE 3851 --- [nio-8080-exec-7] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [770605]
2020-11-10 22:30:41.729 DEBUG 3851 --- [nio-8080-exec-7] tributeConverterSqlTypeDescriptorAdapter : Converted value on binding : 2020-11-10T22:30:41.718 -> 2020-11-10 22:30:41.718
2020-11-10 22:30:41.729 TRACE 3851 --- [nio-8080-exec-7] o.h.type.descriptor.sql.BasicBinder : binding parameter [2] as [TIMESTAMP] - [2020-11-10 22:30:41.718]
2020-11-10 22:30:41.729 TRACE 3851 --- [nio-8080-exec-7] o.h.type.descriptor.sql.BasicBinder : binding parameter [3] as [VARCHAR] - [kido]
2020-11-10 22:30:41.729 TRACE 3851 --- [nio-8080-exec-7] o.h.type.descriptor.sql.BasicBinder : binding parameter [4] as [VARCHAR] - [kido]
2020-11-10 22:30:41.732 INFO 3851 --- [nio-8080-exec-7] c.s.practical.simpleboard.entity.User : fireEvent PostPersist:
Hibernate:
insert
into
user_detail
(avatar_img, category, joined_at, modified_at, nick, id)
values
(?, ?, ?, ?, ?, ?)
로그 결과를 확인해보면 다음과 같이 처리된다.
- fireEvent PrePersist
- select / insert 수행
- fireEvent PostPersist
위 순서대로 수행이 됨을 확인할 수 있다.
지금까지 수행한 작업은 Entity 객체에 해당 어노테이션을 걸어 주었으며, 데이터를 삽입시 수행됨을 확인할 수 있다.
수정 요청 처리하기.
2020-11-10 22:34:22.490 INFO 3851 --- [nio-8080-exec-1] c.s.practical.simpleboard.entity.User : fireEvent PreUpdate
Hibernate:
update
user
set
birth=?,
created_at=?,
name=?
where
id=?
2020-11-10 22:34:22.491 TRACE 3851 --- [nio-8080-exec-1] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [770707]
2020-11-10 22:34:22.491 DEBUG 3851 --- [nio-8080-exec-1] tributeConverterSqlTypeDescriptorAdapter : Converted value on binding : 2020-11-10T22:30:41.718 -> 2020-11-10 22:30:41.718
2020-11-10 22:34:22.491 TRACE 3851 --- [nio-8080-exec-1] o.h.type.descriptor.sql.BasicBinder : binding parameter [2] as [TIMESTAMP] - [2020-11-10 22:30:41.718]
2020-11-10 22:34:22.491 TRACE 3851 --- [nio-8080-exec-1] o.h.type.descriptor.sql.BasicBinder : binding parameter [3] as [VARCHAR] - [kido2]
2020-11-10 22:34:22.491 TRACE 3851 --- [nio-8080-exec-1] o.h.type.descriptor.sql.BasicBinder : binding parameter [4] as [VARCHAR] - [kido]
2020-11-10 22:34:22.494 INFO 3851 --- [nio-8080-exec-1] c.s.practical.simpleboard.entity.User : fireEvent PostUpdate
로그 결과를 확인하면 다음과 같다.
- fireEvent PreUpdate 이벤트 호출
- Update 수행
- fireEvent PostUpdate 이벤트 호출
위 순서처럼 업데이트 수정전/후 로 호출이 일어남을 확인할 수 있다.
조회 요청 처리하기.
조회를 수행하면 어떠한 이벤트가 발생하는지 확인해보자.
Hibernate:
select
userdetail0_.id as id1_7_0_,
userdetail0_.avatar_img as avatar_i2_7_0_,
userdetail0_.category as category3_7_0_,
userdetail0_.joined_at as joined_a4_7_0_,
userdetail0_.modified_at as modified5_7_0_,
userdetail0_.nick as nick6_7_0_
from
user_detail userdetail0_
where
userdetail0_.id=?
2020-11-10 22:45:08.690 TRACE 3851 --- [nio-8080-exec-3] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [kido]
2020-11-10 22:45:08.692 TRACE 3851 --- [nio-8080-exec-3] o.h.type.descriptor.sql.BasicExtractor : extracted value ([avatar_i2_7_0_] : [VARCHAR]) - [http://kido.com/img.png]
2020-11-10 22:45:08.692 TRACE 3851 --- [nio-8080-exec-3] o.h.type.descriptor.sql.BasicExtractor : extracted value ([category3_7_0_] : [VARCHAR]) - [Computer Science]
2020-11-10 22:45:08.692 TRACE 3851 --- [nio-8080-exec-3] o.h.type.descriptor.sql.BasicExtractor : extracted value ([joined_a4_7_0_] : [TIMESTAMP]) - [2020-11-10 22:30:41.718]
2020-11-10 22:45:08.692 DEBUG 3851 --- [nio-8080-exec-3] tributeConverterSqlTypeDescriptorAdapter : Converted value on extraction: 2020-11-10 22:30:41.718 -> 2020-11-10T22:30:41.718
2020-11-10 22:45:08.693 TRACE 3851 --- [nio-8080-exec-3] o.h.type.descriptor.sql.BasicExtractor : extracted value ([modified5_7_0_] : [TIMESTAMP]) - [2020-11-10 22:30:41.718]
2020-11-10 22:45:08.693 DEBUG 3851 --- [nio-8080-exec-3] tributeConverterSqlTypeDescriptorAdapter : Converted value on extraction: 2020-11-10 22:30:41.718 -> 2020-11-10T22:30:41.718
2020-11-10 22:45:08.693 TRACE 3851 --- [nio-8080-exec-3] o.h.type.descriptor.sql.BasicExtractor : extracted value ([nick6_7_0_] : [VARCHAR]) - [ddo]
2020-11-10 22:45:08.693 INFO 3851 --- [nio-8080-exec-3] c.s.practical.simpleboard.entity.User : fireEvent PostLoad
- select 가 수행된다.
- fireEvent PostLoad 가 호출된다.
조회의 경우 PostLoad 가 호출이 일어난다.
삭제 요청 처리하기.
이번에는 삭제인경우를 확인해보자.
2020-11-10 22:46:43.699 INFO 3851 --- [nio-8080-exec-6] c.s.practical.simpleboard.entity.User : fireEvent PreRemove
Hibernate:
delete
from
user_role
where
user_id=?
2020-11-10 22:46:43.700 TRACE 3851 --- [nio-8080-exec-6] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [kido]
Hibernate:
delete
from
user_detail
where
id=?
2020-11-10 22:46:43.703 TRACE 3851 --- [nio-8080-exec-6] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [kido]
Hibernate:
delete
from
user
where
id=?
2020-11-10 22:46:43.707 TRACE 3851 --- [nio-8080-exec-6] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [kido]
2020-11-10 22:46:43.711 INFO 3851 --- [nio-8080-exec-6] c.s.practical.simpleboard.entity.User : fireEvent PostRemove
- fireEvent PreRemove 가 호출된다.
- 삭제 및 cascade 된 객체가 모두 삭제된다.
- fireEvent PostRemove 가 호출된다.
보는바와 같이 삭제도 삭제 전/후가 호출됨을 확인 할 수 있다.
Event Listener 생성하기.
이번에는 Entity에 직접 거는 대신에 EntityEventListener 를 생성하는 방법을 알아보자.
새로운 클래스인 RoleEventTrailListener.java 를 생성하자.
package com.schooldevops.practical.simpleboard.entity.listener;
import com.schooldevops.practical.simpleboard.entity.RoleEntity;
import lombok.extern.slf4j.Slf4j;
import javax.persistence.*;
@Slf4j
public class RoleEventTrailListener {
@PrePersist
@PreUpdate
@PreRemove
private void beforeAnyUpdate(RoleEntity role) {
if (role.getId() == null) {
log.info("Brand New Role Insert Event. ");
} else {
log.info("Update or delete Role: " + role.getId());
}
}
@PostPersist
@PostUpdate
@PostRemove
private void afterAnyUpdate(RoleEntity role) {
log.info("after add/update/delete complete for role: " + role.getId());
}
@PostLoad
private void afterLoad(RoleEntity role) {
log.info("role loaded from database: " + role.getId());
}
}
우리는 3개의 메소드에 각각 이벤트 어노테이션을 걸어주었다.
@PrePersist
@PreUpdate
@PreRemove
private void beforeAnyUpdate(RoleEntity role)
이것처럼 beforeAndUpdate 메소드에는 저장전, 수정전, 삭제전 처리를 할 수 있도록 콜백 메소드를 만들었다.
@PostPersist
@PostUpdate
@PostRemove
private void afterAnyUpdate(RoleEntity role)
이제는 afterAnyUpdate 메소드에는 저장후, 수정후, 삭제후 처리를 할 수 있도록 콜백 메소드를 만들었다.
@PostLoad
private void afterLoad(RoleEntity role)
afterLoad 는 엔터티를 조회한 후 호출이 된다.
위와 같이 이벤트를 만들 수 있으며, 우리는 Role 엔터티가 이벤트를 받을 수 있도록 할 것이기 때문에
RoleEntity.java 에 다음과 같이 어노테이션을 붙여준다.
@EntityListeners(RoleEventTrailListener.class)
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Getter
... 생략
@EntityListeners(RoleEventTrailListener.class) 을 걸어주면 RoleEntity 의 CRUD 상황에 따라 이벤트가 발생함을 확인할 수 있다.
생성 이벤트.
2020-11-10 22:53:23.133 INFO 3851 --- [nio-8080-exec-8] c.s.p.s.e.l.RoleEventTrailListener : Brand New Role Insert Event.
Hibernate:
insert
into
role
(name)
values
(?)
2020-11-10 22:53:23.134 TRACE 3851 --- [nio-8080-exec-8] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [Administrator]
2020-11-10 22:53:23.136 INFO 3851 --- [nio-8080-exec-8] c.s.p.s.e.l.RoleEventTrailListener : after add/update/delete complete for role: 2
- Brand New Role Insert Event. 가 호출됨
- insert 가 수행됨
- after add/update/delete complete for role: 2 가 호출됨.
위와 같이 이벤트가 호출 되었다.
우리가 지정한 @PrePersist 와 @PostPersist 에 대해서 이벤트가 발생함을 확인할 수 있다.
조회 이벤트 확인하기.
Hibernate:
select
roleentity0_.id as id1_3_0_,
roleentity0_.name as name2_3_0_
from
role roleentity0_
where
roleentity0_.id=?
2020-11-10 22:55:42.410 TRACE 3851 --- [nio-8080-exec-1] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [BIGINT] - [2]
2020-11-10 22:55:42.413 TRACE 3851 --- [nio-8080-exec-1] o.h.type.descriptor.sql.BasicExtractor : extracted value ([name2_3_0_] : [VARCHAR]) - [Administrator]
2020-11-10 22:55:42.413 TRACE 3851 --- [nio-8080-exec-1] org.hibernate.type.CollectionType : Created collection wrapper: [com.schooldevops.practical.simpleboard.entity.RoleEntity.users#2]
2020-11-10 22:55:42.413 INFO 3851 --- [nio-8080-exec-1] c.s.p.s.e.l.RoleEventTrailListener : role loaded from database: 2
- select 수행
- role loaded from database: 2 가 호출됨.
select 인경우 @PostLoaded 가 호출됨을 확인했다.
Wrapup
이처럼 JPA 엔터티의 라이프사이클에 따라 다양한 이벤트 훅을 활용해보았고, 엔터티에 직접 연동하는 방법, EventListener 를 이용하는 방법을 각각 알아 보았다.
이벤트 훅은 데이터의 변경 사항을 기록하거나, 중간에 처리해야할 사항들을 적절히 활용할 수 있도록 해준다.