일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- Spring Boot
- QueryDSL
- kotlin
- springdatajpa
- transaction
- 알고리즘
- 그리디
- JPQL
- JDBC
- db
- Servlet
- 김영한
- 스프링
- Proxy
- 스프링 핵심 기능
- AOP
- pointcut
- java
- jpa
- 자바
- spring
- SpringBoot
- http
- Exception
- 인프런
- Greedy
- 스프링 핵심 원리
- Thymeleaf
- 백준
- Android
- Today
- Total
개발자되기 프로젝트
Entity Listener - 1 본문
Listener : 이벤트를 관찰하고 있다가 이벤트가 발생하면 특정 동작을 진행하는 것을 말한다.
Entity Listener는 Entity가 동작하는 몇 가지 방법에 대한 이벤트를 관찰하고 있음.
1. Listener 관련 annotaions
@PrePersist | Persist, insert method 실행 전(해당 엔티티 저장 전) |
@PreUpdate | Merge method 실행 전(해당 엔티티 업데이트 전) |
@PreRemove | Delete method 실행 전(해당 엔티티 삭제 전) |
@PostPersist | Persist method 실행 후(해당 엔티티 저장 후) |
@PostUpdate | Merge method 실행 후(헤당 엔티티 업데이트 후) |
@PostRemove | Delete method 실행 후(해당 엔티티 제거 후) |
@PostLoad | Select조회 직후(해당 엔티티 조회 후) |
먼저 각 항목이 언제 작동되는지 살펴보자.
만들어둔 User class에 다음과 같이 추가한다.
@PrePersist
public void prePersist(){
System.out.println(">>>>pre Persist");
}
@PostPersist
public void postPersist(){
System.out.println(">>>>post Persist");
}
@PreUpdate
public void preUpdate(){
System.out.println(">>>>>>pre Update");
}
@PostUpdate
public void postUpdate(){
System.out.println(">>>>>>>post Update");
}
@PreRemove
public void preRemove(){
System.out.println(">>>>pre Remove");
}
@PostRemove
public void postRemove(){
System.out.println(">>>>post remove");
}
@PostLoad
private void postLoad(){
System.out.println(">>>>post load");
}
insert, update, delete, select모두 동작하도록 테스트 코드를 적어준당
@Test
void listenerTest(){
User user = new User();
user.setEmail("hyun10@navber.com");
user.setName("hyun10");
userRepository.save(user);
User user2 = userRepository.findById(1L).orElseThrow(RuntimeException::new);
user2.setName("hyyyyyyyyyyyyyyun");
userRepository.save(user2);
userRepository.deleteById(4L);
}
실행결과 각 항목이 작동하는 위치? 를 알 수 있다. 닉값 그대로 한다.
>>>>pre Persist
Hibernate:
call next value for hibernate_sequence
Hibernate:
insert
into
user
(created_at, email, gender, name, updated_at, id)
values
(?, ?, ?, ?, ?, ?)
>>>>post Persist
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=?
>>>>post load
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=?
>>>>post load
>>>>>>pre Update
Hibernate:
update
user
set
created_at=?,
email=?,
gender=?,
name=?,
updated_at=?
where
id=?
>>>>>>>post Update
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=?
>>>>post load
>>>>pre Remove
Hibernate:
delete
from
user
where
id=?
>>>>post remove
실제로 사용하는 예를 들어보자, data를 저장할 때 updated time, created time을 다음과 같이 set을 해준다고 하자.
@Test
void prePersistTest(){
User user = new User();
user.setEmail("hyun10@navver.com");
user.setName("hyun10");
user.setCreatedAt(LocalDateTime.now());
user.setUpdatedAt(LocalDateTime.now());
userRepository.save(user);
System.out.println(userRepository.findByEmail("hyun10@navver.com"));
}
그런데 이러한 경우는 실수로 해당 코드를 지워버려 데이터 정합성에 문제가 생길 수 있다.ㅜ
그래서 아예~ Entity에 @PrePersist설정을 하면 실수없이 data 저장 전에 원하는 기능을 수행할 수 있다.
그러면 앞에서 작성한 prePersist method를 살짝 수정해보자.
@PrePersist를 지정하여 insert전에 생성시간, 업데이트 시간을 입력하도록 구성했다.
@PrePersist
public void prePersist(){
this.createdAt = LocalDateTime.now(); //자기의 값을 변경시키기 때문에.
this.updatedAt = LocalDateTime.now();
}
실행 결과를 보면 생성/업데이트 시간이 입력된 것을 볼 수있당.
Hibernate:
select
user0_.id as id1_0_,
user0_.created_at as created_2_0_,
user0_.email as email3_0_,
user0_.gender as gender4_0_,
user0_.name as name5_0_,
user0_.updated_at as updated_6_0_
from
user user0_
where
user0_.email=?
User(name=hyun10, email=hyun10@navver.com,
createdAt=2021-06-15T22:02:52.202511,
updatedAt=2021-06-15T22:02:52.202511, id=6, testData=null, gender=null)
업데이트도 유사하게 작성해주면 된다.
@PreUpdate
public void preUpdate(){
this.updatedAt = LocalDateTime.now();
}
@Test
void preUpdateTest(){
User user = userRepository.findById(1L).orElseThrow(RuntimeException::new);
System.out.println("as - is : " + user);
user.setName("lalalalalalalala");
userRepository.save(user);
System.out.println(" to be : " + userRepository.findAll().get(0));//이후 영속성 공부시...
}
업데이트 시간이 변경되었당
as - is : User(name=hyun, email=hyun@naver.com,
createdAt=2021-06-15T22:12:47.858239,
updatedAt=2021-06-15T22:12:47.858239, id=1, testData=null, gender=null)
to be : User(name=lalalalalalalala, email=hyun@naver.com,
createdAt=2021-06-15T22:12:47.858239,
updatedAt=2021-06-15T22:12:49.365017, id=1, testData=null, gender=null)
2. Book manager 만들기
Listener를 적용하기 전에 User는 그만 사용하고 Book을 만들자.
- Book class
package com.jpa.bookmanager.domain;
import ch.qos.logback.core.joran.spi.NoAutoStart;
import javax.persistence.*;
import lombok.*;
import java.time.LocalDateTime;
@Entity
@NoArgsConstructor
@Data
public class Book {
@Id
@GeneratedValue
private Long id;
private String name;
private String author;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
@PrePersist
public void prePersist(){
this.createdAt = LocalDateTime.now();
this.updatedAt = LocalDateTime.now();
}
@PreUpdate
public void preUpdated(){
this.updatedAt = LocalDateTime.now();
}
}
- BookRepository
package com.jpa.bookmanager.repository;
import com.jpa.bookmanager.domain.Book;
import org.springframework.data.jpa.repository.JpaRepository;
public interface BookRepository extends JpaRepository<Book, Long> {//enum타입, id타입
}
- BookRepositoryTest
package com.jpa.bookmanager.repository;
import com.jpa.bookmanager.domain.Book;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class BookRepositoryTest {
@Autowired //Bookrepository와 연결?
private BookRepository bookRepository;
@Test
void bookTest(){
Book book = new Book();
book.setName("에반게리온");
book.setAuthor("안노");
bookRepository.save(book);
System.out.println(bookRepository.findAll());
}
}
여기서 의문점. 어느 entity나 생성시간, 업데이트 시간은 필요할텐데.. 매 번 이렇게 해야하나..??
이때! Listener를 사용한다. 즉 Entity Listener를 만들어서 여러 Entity에 사용을 하자!
1) MyentityListener Class
ackage com.jpa.bookmanager.domain;
public class MyEntityListener {
}
2) Book class에 @EntityListeners 지정, value로는 Listener.class를 가져온다.
이를 통해 entity와 Listener가 연결된다?
(기존 @PrePersist, @PreUpdate는 주석처리)
package com.jpa.bookmanager.domain;
import ch.qos.logback.core.joran.spi.NoAutoStart;
import javax.persistence.*;
import lombok.*;
import java.time.LocalDateTime;
@Entity
@NoArgsConstructor
@Data
@EntityListeners(value = MyEntityListener.class)
public class Book {
@Id
@GeneratedValue
private Long id;
private String name;
private String author;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
/*@PrePersist
public void prePersist(){
this.createdAt = LocalDateTime.now();
this.updatedAt = LocalDateTime.now();
}
@PreUpdate
public void preUpdated(){
this.updatedAt = LocalDateTime.now();
}*/
}
3) Auditable interface 선언
우리가 사용할 리스너의 역할은 insert 전, update 전 생성/업데이트 시간을 입력하는 역할을 한다.
그러기 위해서는 리스너가 createdAt, updatedAt이라는 변수를 알고 있어야 한다.
이를 위해 interface를 하나 선언해주자. 리스너를 사용할 class에서 구현할 예정이다.
lombok을 사용하고 있기 때문에, 직접 보이지는 않지만
getCreatedAt(), getUpdatedAt()등 getter는 이미 존재한다.
package com.jpa.bookmanager.domain;
import java.time.LocalDateTime;
public interface Auditable {
LocalDateTime getCreatedAt();
LocalDateTime getUpdatedAt();
void setCreatedAt(LocalDateTime createdAt);
void setUpdatedAt(LocalDateTime updatedAt);
}
4) 그다음 Book class에서 implement를 해보자. 이미 다 구현이 되어있어서 에러안뜬다!(@Data)
package com.jpa.bookmanager.domain;
import ch.qos.logback.core.joran.spi.NoAutoStart;
import javax.persistence.*;
import lombok.*;
import java.time.LocalDateTime;
@Entity
@NoArgsConstructor
@Data
@EntityListeners(value = MyEntityListener.class)
public class Book implements Auditable {
@Id
@GeneratedValue
private Long id;
private String name;
private String author;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
/*@PrePersist
public void prePersist(){
this.createdAt = LocalDateTime.now();
this.updatedAt = LocalDateTime.now();
}
@PreUpdate
public void preUpdated(){
this.updatedAt = LocalDateTime.now();
}*/
}
5) MyEntityListener 작성
이제 진짜로 리스너를 만들어 보자.
* Listener는 해당 Entity를 받아서 처리를 해줘야 한다. 어떤 타입인지 모르니 Object로 받는다.
이전에 Book class에서 단순히 PrePersist()함수로 구현했을 때는 this로 entity를 받을 수 있던 것과 차이점이 있따.
package com.jpa.bookmanager.domain;
import javax.persistence.PrePersist;
import javax.persistence.PreUpdate;
import java.time.LocalDateTime;
public class MyEntityListener {
@PrePersist //리스너는 해당 entity를 받아서! 처리를 해줘야 한다.
public void prePersist(Object o){
if(o instanceof Auditable){
//Auditable 객체다? 이 리스너가 사용될 엔티티다?
((Auditable) o).setCreatedAt(LocalDateTime.now());
((Auditable) o).setUpdatedAt(LocalDateTime.now());
}
}
@PreUpdate //리스너는 해당 entity를 받아서! 처리를 해줘야 한다.
public void preUpdate(Object o){
if(o instanceof Auditable){
//Auditable 객체다? 이 리스너가 사용될 엔티티다?
((Auditable) o).setUpdatedAt(LocalDateTime.now());
}
}
}
6) Book class는 Autitable 인터페이스를 구현하고, Book에 entity Listener를 등록을 해줬다.
따라서 entity에 insert / update method 가 실행되기 전 리스너가 이를 감지하고 해당 entity를 받아와서
entity의 createdAt, updatedAt을 입력할 수 있게 된다!
Hibernate:
call next value for hibernate_sequence
Hibernate:
insert
into
book
(author, created_at, name, updated_at, id)
values
(?, ?, ?, ?, ?)
Hibernate:
select
book0_.id as id1_0_,
book0_.author as author2_0_,
book0_.created_at as created_3_0_,
book0_.name as name4_0_,
book0_.updated_at as updated_5_0_
from
book book0_
[Book(id=6, name=에반게리온, author=안노, createdAt=2021-06-15T22:51:29.542252, updatedAt=2021-06-15T22:51:29.542252)]
결론! 이런 방식으로 리스너를 만들면 여러 entity에서 사용이 가능하다!
'JPA' 카테고리의 다른 글
Entity Listener : 3(@AuditingEntityListener) (0) | 2021.06.16 |
---|---|
Entity Listener : 2 (0) | 2021.06.16 |
@Entity 속성 2 (0) | 2021.06.15 |
@Entity 기본 속성 - 1 (0) | 2021.06.15 |
Query Method : paging (0) | 2021.05.27 |