이번 내용은 Entity 의 변경사항이 있을 때 뭔가를 할 수 있는 엔티티 이벤트 리스너입니다.
강의 진행은 두 실습 중 하나만... 2배속으로 들어도 30분이면 중간에 멈추고 다시보고 본 내용 정리까지 하면 길어요. 이런 저런 이유로 앞에거만.
방법은 두 가지입니다.
- 엔티티에 직접 달기 (어노테이션을 단 메서드로)
- 별도의 클래스로 만들고 어노테이션으로 엔티티에 붙이기
- 방법 자체는 위에랑 똑같은데, 정의하는 클래스가 바뀌고, 패러미터로 대상 클래스 (Object o) 가 추가됩니다.
엔티티 리스너 어노테이션들
조합식으로... 이름부터가 조합 방식으로 구성됩니다.
- 시점: Pre / Post,
- 작업: Persist / Update / Delete / Load
시점 + 작업 순으로 이름이 정해지는데 Load 는 Pre 시점이 없습니다. 그도 그럴게 데이터를 로드하지도 않았는데 뭘 할 수는 없잖아요?
- @PrePersist / @PostPersist - insert 쿼리 실행 시점 (전 / 후)
- @PreUpdate / @PostUpdate - update 쿼리 실행 시점 (전 / 후)
- 강의에서 강사님께서 "merge 실행 전 후" 라고 언급하셨는데 자세한 설명은 없었습니다. 뭔가 기저 로직의 실행방법에 대한 언급인 듯 합니다.
- @PreDelete / @PostDelete - delete 쿼리 실행 시점 (전 / 후)
- @PostLoad - select 쿼리 실행 전
정의는 다음과 같이 Entity 의 멤버 메소드로 합니다.
엔티티 생략
public class User {
생략
@PrePersist
private void 내맘대로_메서드_이름() {
여기에_로그를_찍어보세요();
}
생략
}
엔티티 리스너가 쓰이는 시점 - Audit
배웠으면 사용처도 알아야죠. 엔티티 리스너는 Auditing ("감사") 이라고 해서 생성된 시점, 업데이트된 시점 등을 확인하는 용도로 자주 쓰인답니다. 그래서 사용되는 리스너는 @PrePersist (insert 전), @PreUpdate (update 전) 이렇게 두 가지라네요.
(저는 그 제품을 담당하고 있지 않지만) 자사 제품이 "감사추적증명서" 라는 기능을 제공하는데, 누가 어떤 입력을 언제 했었는지가 리스트로 쭉 나오더라구요. 이상한 짓을 했는지 아닌지 확인하는 기능이 감사인가보네요.
생략
public class User {
생략
@Column(updatable = false)
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
@PrePersist
private void setPersistedAt() {
createdAt = LocalDateTime.now();
updatedAt = LocalDateTime.now();
}
@PreUpdate
private void setUpdatedAt() {
updatedAt = LocalDateTime.now();
}
생략
}
이 시점에서 잘 생각해봐야하는게 있습니다.
- @PrePersist 가 과연 업데이트 쿼리 이전에도 동작할까? -> 아니오
- 제가 헷갈렸던 부분입니다.
- 동작하지 않습니다. 업데이트는 없던 데이터를 새로 영속성 저장소에 넣는 행위가 아니기 때문이죠.
- updatedAt 도 Persist 전에 넣어줘야할까? -> 예, 강의에서는 그렇게 함
- 엔티티를 영속하는 시점에서는 updatedAt 을 null 로 두는 것도 방법이겠지만, null 처리 자체가 실수할 여지가 많다는 점을 생각해봤을 때 그냥 updatedAt 도 객체 생성 시에 같이 넣어주는 게 좋겠습니다.
강의에서는 이 사항들은 언급되진 않았지만... 헷갈렸던 것들은 머리에 이정표를 남기는 역할을 해서 배운 내용을 기억에 남기는데 크게 도움이 되니까요.
추가 의문: createdAt, updatedAt 은 DB단에서 직접 설정해줘야 하는 거 아닌가?? -> 역시 편의성 문제로 그렇게 안 하는 거겠죠?? 굳이 DB에서 직접 auditing 정보를 업데이트해서 얻는 이점이 없으니까...
여러 엔티티에 적용하기: Listener 객체를 별도로 만들어!
똑같은 코드를 반복하는 게 개발자들이 마아아아않이 싫어하는 것들 중 하나입니다
지금까지 실습중인 코드에는 User 라는 엔티티 하나밖에 없었지만, 코드가 점점 커져서 Book 이라는 엔티티가 생기고 거기에도 생성시간/갱신시간을 추적하는 기능을 넣어야 한다고 생각해봅시다. 복사 & 붙여넣기 라는 디지털 문명의 비기를 사용하실건가요? 반복하지 않아도 될 걸 굳이 반복하는 건 비기가 아니라 비계같은 겁니다.
그래서, 리스너 객체로 별도로 만들어서 어노테이션으로 객체에 붙입니다. 방법은 거의 똑같아요.
// 언어가 코틀린이 됐다 자바가 됐다 하니까 재밌지 않으세요?
//
// 인터페이스 이름은 마음대로 하면 됩니다.
interface IAuditable {
var createdAt: LocalDateTime
var updatedAt: LocalDateTime
}
인터페이스는 이걸 실습하려는 목적으로는 굳이 필요하지는 않은데, 자연스럽게 추가되는 거라고 보시면 되겠습니다. 감사 가능한 엔티티니까 당연히 IAuditable 같은 인터페이스를 상속해야 하지 않을까요???
강의에서는 코틀린을 전혀 사용하지 않고 있고, 저는 구구절절 Java 를 다 써넣을 생각이 없어서 kotlin 으로 썼지만 위 코드에는 확실히 인지하고 넘어가야 하는 중요한 점이 있습니다. getter와 setter 가 인터페이스에 숨어있다는 겁니다. Java 쪽에서는 get / set 메서드로 필드같아보이는 저걸 구현할거거든요.
@Data // Lombok 이 Get Set 을 만들어줌, 인터페이스 구현 개꿀
생략
@EntityListeners(value = {AuditListener.class}) // 리스너 클래스를 지정합니다.
public class User implements IAuditable {
생략
@Column(updatable = false)
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
}
데이터 OK, 이제 리스너 클래스만 남았습니다.
public class AuditListener {
@PrePersist
public void prePersist(Object o) {
if (o instanceof IAuditable) {
IAuditable auditable = (IAuditable) o;
auditable.setCreatedAt(LocalDateTime.now());
auditable.setUpdatedAt(LocalDateTime.now());
}
}
@PreUpdate
public void preUpdate(Object o) {
if (o instanceof IAuditable) {
IAuditable auditable = (IAuditable) o;
auditable.setUpdatedAt(LocalDateTime.now());
}
}
}
위에 했던거랑 똑같죠? Object가 패러미터로 들어간다는 거만 빼면요. 어째 Java 놈 as도 안되고 as?도 안 되고...
메서드 이름도 마음대로 해도 됩니다. 다만 이번에는 IntelliJ 가 생성해주는 이름을 그대로 썼어요. 생성이 된다는 게 놀랍네요. 인터페이스도 없는 쌩 클래스를 클래스 이름만으로 Listener 를 생성해주겠다고 말하는 IntelliJ 너란 IDE...
본 포스팅은 패스트캠퍼스 환급 챌린지 참여를 위해 작성되었습니다.
패스트캠퍼스: https://bit.ly/37BpXiC
#패스트캠퍼스 #패캠챌린지 #직장인인강 #직장인자기계발 #패스트캠퍼스후기 #한번에끝내는JavaSpring웹개발마스터초격차패키지Online
아니 그 오늘은 좀 빨리 끝났다구요... 하나만 봐서... 느낌상... 실제로는 최소 30분은 잡아먹은 거 같지만...
Spring Data JPA 에 기능이 진짜 많은 게 굉장히 좋아보입니다. 사람들이 스프링 스프링 하는 이유가 있네요.
'FastCampus - 한번에 끝내는 Java|Spring 웹 개발 > 04 JPA' 카테고리의 다른 글
JPA Ch 6 릴레이션, ERD, 1:1 - 패스트캠퍼스 챌린지 26일차 (0) | 2022.02.18 |
---|---|
JPA Ch 5 Entity Listener (2) - 패스트캠퍼스 챌린지 25일차 (0) | 2022.02.17 |
JPA Ch 4 Entity 기본 어노테이션들 - 패스트캠퍼스 챌린지 23일차 (0) | 2022.02.15 |
JPA Ch 3 Query Method 소팅 / 페이징 - 패스트캠퍼스 챌린지 22일차 (0) | 2022.02.14 |
JPA Ch 3 Query Method (2~3) - 패스트캠퍼스 챌린지 21일차 (0) | 2022.02.13 |