JPA EntityListener
🤔EntityListener??
엔티티 리스너란? 이벤트를 관찰하고 있다가, 이벤트가 발생하면 특정 동작을 진행하는 것. Entity리스너 이기 때문에 엔티티가 동작하는 몇가지 방법에 이벤트를 리스닝 하고 있다.
✅ Entity에 Listener설정
@PrePersist // Insert호출 전 실행
@PreUpdate // marge 호출 전 실행
@PreRemove // Delete호출 전 실행
@PostPersist // Insert호출 후 실행
@PostUpdate // marge 호출 후 실행
@PostRemove // Delete호출 후 실행
@PostLoad // select호출 후 실행
UserEntity
...
...
@PrePersist
public void prePersist(){
System.out.println(">>> prePersist");
}
@PreRemove
public void preRemove(){
System.out.println(">>> preRemove");
}
@PostPersist
public void postPersist(){
System.out.println(">>> postPersist");
}
@PostUpdate
public void postUpdate(){
System.out.println(">>> postUpdate");
}
@PostRemove
public void postRemove(){
System.out.println(">>> postRemove");
}
@PostLoad
public void postLoad(){
System.out.println(">>> PostLoad");
}
...
...
위와같이 Entity에 설정해 두면 trigger처럼 쿼리가 실행된다.
✅ 생성시간과 수정시간 (Default설정??)
현업에서는 User같은 Entity를 만들때 생성 일, 수정 일 을 넣는게 국룰이다.
그럼
@Test
void prePersistTest() {
User user = new User();
user.setEmail("[email protected]");
user.setName("martin");
user.setCreatedAt(LocalDateTime.now());
user.setUpdatedAt(LocalDateTime.now());
userRepository.save(user);
System.out.println(userRepository.findByEmail("[email protected]"));
}
🤔🤔 위의 코드 처럼 일일이 setCreatedAt,setUpdatedAt 을 해줘야 할까?
Dont Repeat Yourself DRY 법칙에 어긋난다. 그리고 개발자가 실수로 CreatedAt을 넣지 않으면 데이터 정확성이 떨어지게 된다.
그래서 Entity에 prePersist 설정을 해서 자동으로 해당 값을 Set하는 방법을 실행하는게 좋다.
@PrePersist
public void prePersist(){
System.out.println(">>> prePersist");
this.createdAt = LocalDateTime.now();
this.updatedAt = LocalDateTime.now();
}
@PreRemove
public void preUpdate(){
System.out.println(">>> preUpdate");
this.updatedAt = LocalDateTime.now();
}
TEST 해보자
@Test
void prePersistTest() {
User user = new User();
user.setEmail("[email protected]");
user.setName("martin");
userRepository.save(user);
System.out.println(userRepository.findByEmail("[email protected]"));
}
@Test
void preUpdateTest(){
User user = userRepository.findById(1L).orElseThrow(RuntimeException::new);
System.out.println("as - is (기존 유저값) : " + user);
user.setName("marttttin2");
userRepository.save(user);
System.out.println("to - be (변경된 값) : " + userRepository.findAll().get(0));
}
>>> prePersist
Hibernate:
call next value for hibernate_sequence
Hibernate:
insert
into
user
(created_at, email, gender, name, updated_at, id)
values
(?, ?, ?, ?, ?, ?)
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=?
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=?
>>> preUpdate
Hibernate:
update
user
set
created_at=?,
email=?,
gender=?,
name=?,
updated_at=?
where
id=?
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_
to - be (변경된 값) : User(id=1, name=marttttin, [email protected], gender=null, createdAt=2022-02-06T17:50:52.948, updatedAt=2022-02-06T17:50:53.213)
EntityListener 사용
🤔 그럼 엔티티리스너는 언제 나오냐?
먼저 setting 하자
Book(Entity)와
@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Book {
@Id
@GeneratedValue
private Long id;
private String name;
private String author;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
@PrePersist
public void prePersist() {
System.out.println(">>> prePersist");
this.createdAt = LocalDateTime.now();
this.updatedAt = LocalDateTime.now();
}
@PreUpdate
public void preUpdate() {
System.out.println(">>> preUpdate");
this.updatedAt = LocalDateTime.now();
}
}
BookRepository와
public interface BookRepository extends JpaRepository<Book, Long> {
}
BookRepositoryTest를 만들고
@SpringBootTest
public class BookRepositoryTest {
@Autowired
private BookRepository bookRepository;
@Test
void bookTest() {
Book book = new Book();
book.setName("Jpa 초격차 패키지");
book.setAuthor("패스트캠퍼스");
bookRepository.save(book);
System.out.println(bookRepository.findAll());
}
}
🤚 자 그럼 여기서 Entity에 직접 @PrePersist같은 어노테이션을 계속 써야할까? createdAt updatedAt은 계속 써야하기때문에 Entity에 적어도 결국 코드 중복이다. 이럴때 사용하는 것이 EntityListener이다.
Auditable 인터페이스 하나 만들어준다. 그럼 User와 Book은 interface를 받아주면된다.
Auditable
public interface Auditable {
LocalDateTime getCreatedAt();
LocalDateTime getUpdatedAt();
void setCreatedAt(LocalDateTime createdAt);
void setUpdatedAt(LocalDateTime updatedAt);
}
Listener를 써보자.
MyEntityListener
public class MyEntityListener {
@PrePersist
public void prePersist(Object o) { //해당 오브젝트를 파라미터로 받아야하기 때문에 오브젝트를 강제한다
if (o instanceof Auditable) { //받아오 오브젝트가 Auditable 객체인지 확인하고
((Auditable) o).setCreatedAt(LocalDateTime.now());
((Auditable) o).setUpdatedAt(LocalDateTime.now());
}
}
@PreUpdate
public void preUpdate(Object o){
if (o instanceof Auditable) { //받아오 오브젝트가 Auditable 객체인지 확인하고
((Auditable) o).setUpdatedAt(LocalDateTime.now());
}
}
}
User,Book클래스위에 리스너를 사용하겠다고 붙인다.
@EntityListeners(value = MyEntityListener.class)
이놈들은 다 주석 처리하고
// @PrePersist
// public void prePersist() {
// System.out.println(">>> prePersist");
// this.createdAt = LocalDateTime.now();
// this.updatedAt = LocalDateTime.now();
// }
//
// @PreUpdate
// public void preUpdate() {
// System.out.println(">>> preUpdate");
// this.updatedAt = LocalDateTime.now();
// }
TEST한번 해보자
@SpringBootTest
public class BookRepositoryTest {
@Autowired
private BookRepository bookRepository;
@Test
void bookTest() {
Book book = new Book();
book.setName("Jpa 초격차 패키지");
book.setAuthor("패스트캠퍼스");
bookRepository.save(book);
System.out.println(bookRepository.findAll());
}
}
그럼 엔티티리스너를 통해 해당값을 주입 할 수 있다.
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=Jpa 초격차 패키지, author=패스트캠퍼스, createdAt=2022-02-06T18:45:20.434, updatedAt=2022-02-06T18:45:20.434)]
위와 같이하면 로직 공통적인 부분에 대해서 리스너를 하나만 구현하고도 여러곳에서 @EntityListen를 통해 참조해서 사용하여 반복적인 코드를 줄일 수 있다.
흔하게 사용하는 Listener
User정보는 수정된 내용의 히스토리가 필요로 할 수 있다. UserHistory를 만들어보자.
✅ 유저엔티티가 동작할때 리스너가 동작해야함으로 User클래스 위에 @EntityListeners를 달아준다.
@EntityListeners(value = {UserEntityListener.class})
✅ UserHistory데이터를 담을 엔티티를 만들어준다.
@Entity
@NoArgsConstructor
@Data
public class UserHistory {
@Id
@GeneratedValue
private Long id;
private Long userId;
private String name;
private String email;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
}
✅ 유저 엔티티 리스너를 만들어준다.
public class UserEntityListener {
/*
* User의 정보가 생성,수정 될때, 그 User객체를 받아서 History정보를 저장한다.
* */
// @Autowired
// private UserHistoryRepository userHistoryRepository;
@PrePersist
@PreUpdate
public void prePersistAndPreUpdate(Object o) {
/*
* 여기서 주의해야할 점은 Listener는 @Autowired로 빈을 가져오지 못한다. 그래서 BeanUtils클래스를 이용해서 주입해 줘야 한다.
* */
UserHistoryRepository userHistoryRepository = BeanUtils.getBean(UserHistoryRepository.class);
User user = (User) o;
UserHistory userHistory = new UserHistory();
userHistory.setUserId(user.getId());
userHistory.setName(user.getName());
userHistory.setEmail(user.getEmail());
userHistoryRepository.save(userHistory);
}
}
✅ Bean을 쓰지못하는 리스너의 때문에 BeanUtils를 사용해 주입시켜줄수있다.
@Component
public class BeanUtils implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
BeanUtils.applicationContext = applicationContext;
}
public static <T> T getBean(Class<T> clazz) {
return applicationContext.getBean(clazz);
} // 클래스의 빈들을 가져오는 메서드
}
✅ Test 해보자.
@Test
void userHistoryTest() {
User user = new User();
user.setEmail("[email protected]");
user.setName("martin-new");
userRepository.save(user);
user.setName("martin-new-new");
userRepository.save(user);
userHistoryRepository.findAll().forEach(System.out::println);
}
✅ 결과 console
User 생성 및 수정 하기전에 History남기는 쿼리
Hibernate:
insert
into
user_history
(created_at, updated_at, email, name, user_id, id)
values
(?, ?, ?, ?, ?, ?)
Hibernate:
insert
into
user
(created_at, updated_at, email, gender, name, id)
values
(?, ?, ?, ?, ?, ?)
Hibernate:
select
user0_.id as id1_2_0_,
user0_.created_at as created_2_2_0_,
user0_.updated_at as updated_3_2_0_,
user0_.email as email4_2_0_,
user0_.gender as gender5_2_0_,
user0_.name as name6_2_0_
from
user user0_
where
user0_.id=?
User 생성 및 수정 하기전에 History남기는 쿼리
Hibernate:
call next value for hibernate_sequence
Hibernate:
insert
into
user_history
(created_at, updated_at, email, name, user_id, id)
values
(?, ?, ?, ?, ?, ?)
Hibernate:
update
user
set
created_at=?,
updated_at=?,
email=?,
gender=?,
name=?
where
id=?
Hibernate:
select
userhistor0_.id as id1_3_,
userhistor0_.created_at as created_2_3_,
userhistor0_.updated_at as updated_3_3_,
userhistor0_.email as email4_3_,
userhistor0_.name as name5_3_,
userhistor0_.user_id as user_id6_3_
from
user_history userhistor0_
UserHistory(id=6, userId=null, name=martin-new, [email protected], createdAt=null, updatedAt=null)
UserHistory(id=8, userId=7, name=martin-new-new, [email protected], createdAt=null, updatedAt=null)
위와 같이 User Insert update 할때 "User 생성 및 수정 하기전에 History남기는 쿼리"가 뜬다.
그리고 히스토리를 select했을때도 잘 들어가 있는걸 확인 할 수 있다.
🤔 하지만 생성일과 수정일 모두 null로 표시되어있으니 UserHistory 엔티티에도 MyEntityListener를 달아주자.
생성시간 수정시간같은 Auditing은 많이 사용되는 기능이기 때문에 기본리스너를 재공해준다. 기본리스너 사용해보자.
✅ @EnableJpaAuditing을 BookmanagerApplication에 달아주자.
@EnableJpaAuditing
public class BookmanagerApplication {
...
}
✅ User,Book,UserHistory 클래스에 MyEntityListener대신 AuditingEntityListener를 추가해준다.
✅ 그 후 아래와 같이 어노테이션을 달아준다.
@CreatedDate // 생성될때 자동으로 now들어간다.
private LocalDateTime createdAt;
@LastModifiedDate // 수정될때 자동으로 now들어간다.
private LocalDateTime updatedAt;
✅ @CreatedBy, @LastModifiedBy는 시간 뿐만아니라 누가 했는지 까지 기록한다.
리펙토링 해보자.
✅ 공통적으로 사용하는 createdAt,updatedAt을 묶어주고 @MappedSuperclass설정 해준다.
@Data
@MappedSuperclass // 해당클래스의 필드를 상속받는 엔티티의 컬럼으로 포함시켜 주겠다.
@EntityListeners(value = AuditingEntityListener.class)
public class BaseEntity {
@CreatedDate
private LocalDateTime createdAt;
@LastModifiedDate
private LocalDateTime updatedAt;
}
최종적으로 아래와 같이 된다.
Author And Source
이 문제에 관하여(JPA EntityListener), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@stpn94/JPA-EntityListener저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)