JPA Entity Listener 사용

Entity Listner 란?

Listener는 Entity에 발생하는 Event를 기반으로 동작하며, Annotation을 기반으로 입력한다.

Pre/Post + Persist/Update/Remove/Load(post) 조합으로 구성된다.

어떤 시점에서 메서드가 실행되는 지 테스트하기 위해 아래와 같이 함수를 작성해보았다.

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
@PrePersist
public void prePersist(){
System.out.println("===> prePersist() method 호출");
}
@PostPersist
public void postPersist(){
System.out.println("===> postPersist() method 호출");
}
@PreUpdate
public void preUpdate(){
System.out.println("===> preUpdate() method 호출");
}
@PostUpdate
public void postUpdate(){
System.out.println("===> postUpdate() method 호출");
}
@PreRemove
public void preRemove(){
System.out.println("===> preRemove() method 호출");
}
@PostRemove
public void postRemove(){
System.out.println("===> postRemove() method 호출");
}
@PostLoad
public void postLoad(){
System.out.println("===> postLoad() method 호출");
}

아래 코드를 통해 user를 INSERT(Persist)하고, findByID()로 SELECT(Load)하고, UPDATE(Update)하고, 마지막으로 DELETE(Remove) 해주었다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Test
void listenerTest(){
User user = new User();
user.setEmail("inwoo2@gmail.com");
user.setName("inwoo");
// INSERT 실행
userRepository.save(user);

User user2 = userRepository.findById(1L).orElseThrow(RuntimeException::new);
user2.setName("eddy");
userRepository.save(user2);

userRepository.deleteById(2L);
}
결과 확인하기
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
===> prePersist() method 호출
Hibernate:
call next value for hibernate_sequence
Hibernate:
insert
into
user
(created_at, email, gender, name, updated_at, id)
values
(?, ?, ?, ?, ?, ?)
===> postPersist() method 호출
Hibernate:
select
user0_.id as id1_0_0_,
user0_.created_at as created_2_0_0_,
user0_.email as email3_0_0_,
user0_.gender as gender4_0_0_,
user0_.name as name5_0_0_,
user0_.updated_at as updated_6_0_0_
from
user user0_
where
user0_.id=?
===> postLoad() method 호출
Hibernate:
select
user0_.id as id1_0_0_,
user0_.created_at as created_2_0_0_,
user0_.email as email3_0_0_,
user0_.gender as gender4_0_0_,
user0_.name as name5_0_0_,
user0_.updated_at as updated_6_0_0_
from
user user0_
where
user0_.id=?
===> postLoad() method 호출
===> preUpdate() method 호출
Hibernate:
update
user
set
created_at=?,
email=?,
gender=?,
name=?,
updated_at=?
where
id=?
===> postUpdate() method 호출
Hibernate:
select
user0_.id as id1_0_0_,
user0_.created_at as created_2_0_0_,
user0_.email as email3_0_0_,
user0_.gender as gender4_0_0_,
user0_.name as name5_0_0_,
user0_.updated_at as updated_6_0_0_
from
user user0_
where
user0_.id=?
===> postLoad() method 호출
===> preRemove() method 호출
Hibernate:
delete
from
user
where
id=?
===> postRemove() method 호출

활용

실제로는 데이터베이스 오디팅에 자주 사용된다. 오디팅(Auditing)이란 선택된 DB 사용자의 활동을 감시하는 프로세스이다.

Entity의 필드 값에 대한 prePersist, preUpdate가 자주 사용한다.

  • INSERT 이전
  • UPDATE 이전

Entity에 직접 구현

Entity에서 아래와 같이 객체의 생성된 날짜수정 날짜를 INSERT 이전, UPDATE 이전에 시행하도록 설정할 수 있다.

1
2
3
4
5
6
7
8
9
10
@PrePersist
public void prePersist(){
this.createdAt = LocalDate.now();
this.updatedAt = LocalDate.now();
}

@PreUpdate
public void preUpdate(){
this.updatedAt = LocalDate.now();
}

Entity Listener class 구현

중복되는 코드를 방지하기위해 별도의 EntityListener class를 구현할 수도 있다.

공통된 field를 가지고 있는 Entity들을 위해 Interface로 구현해주어도 된다.

1. 원하는 field의 getter/setter method()를 Interface로 작성한다.

- 예제에서는 user의 `createdAt`, `updatedAt`을 관리한다.
- 아래와 같이 interface를 구현하고 Implements하였다.
- Lombok의 `@DATA`를 사용하여 override는 해주지 않아도 되지만, 변수 명에 유의하여야한다.
1
2
3
4
5
6
7
public interface Auditable {
LocalDateTime getCreatedAt();
LocalDateTime getUpdatedAt();

void setCreatedAt(LocalDateTime createdAt);
void setUpdatedAt(LocalDateTime updatedAt);
}
1
public class User implements Auditable{

2. EventListner class를 생성하고, Entity에 Annotation을 추가한다.

1
2
3
public class UserEntityListener {
/* 아래에서 구현 */
}

어떠한 클래스를 Listener로 사용할 것인지 아래와 같이 추가해준다.

  • @EntityListeners(value = UserEntityListener.class)

Prameter를 지정하지 않을 시 오류 발생

Entity를 받아서 처리를 해야하지만, 어떠한 타입인지 모르기 때문에 PrameterObject 객체로 지정해야한다.

따라서 아래와 같이 구현해주었다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class UserEntityListener {
@PrePersist
public void prePersist(Object o){
if (o instanceof Auditable){
((Auditable) o).setCreatedAt(LocalDateTime.now());
((Auditable) o).setUpdatedAt(LocalDateTime.now());
}
}

@PreUpdate
public void preUpdate(Object o) {
if (o instanceof Auditable){
((Auditable) o).setUpdatedAt(LocalDateTime.now());
}
}
}
Author

Inwoo Jeong

Posted on

2021-11-11

Updated on

2021-11-11

Licensed under

You need to set install_url to use ShareThis. Please set it in _config.yml.

댓글