Reference. 한 번에 끝내는 Java/Spring 웹 개발 마스터 초격차 패키지 Online
이전 글
5.5. Entity Listener
- Listener: 이벤트를 관찰하고 있다가 발생하면 특정 동작을 진행
1. Entity Listener Annotation
<method 실행 전>@PrePersist
: insert method가 호출되기 전
@PreUpdate
: merge method가 호출되기 전
@PreRemove
: delete method가 호출되기 전
<method 실행 이후>
@PostPersist
: insert method가 호출된 이후
@PostUpdate
: merge method가 호출된 이후
@PostRemove
: delete method가 호출된 이후@PostLoad
: select 조회가 된 직후
메소드 생성
- 어노테이션 기반이므로 method명을 일치할 필요없음 (
@PrePersist
,prePersist
)
@NoArgsConstructor
@AllArgsConstructor
@RequiredArgsConstructor
@Data
@Builder
@Entity
@Table(name="user", indexes = {@Index(columnList = "name")}, uniqueConstraints = {@UniqueConstraint(columnNames = "email")})
public class User {
...
@PrePersist
public void prePersist(){
System.out.println(">>> prePersist");
}
@PostPersist
public void postPersist(){
System.out.println(">>> postPersist");
}
...
}
테스트 코드 및 실행
@Test
void listenerTest(){
User user = new User();
user.setEmail("test@gmail.com");
user.setName("sim");
userRepository.save(user); //insert
User user2 = userRepository.findById(1L).get();
user2.setName("kim");
userRepository.save(user2); //update
userRepository.deleteById(4L);
}
>>> prePersist
Hibernate:
call next value for hibernate_sequence
Hibernate:
insert
into
user
(created_at, email, gender, name, updated_at, id)
values
(?, ?, ?, ?, ?, ?)
>>> postPersist
Hibernate:
select
user0_.id as id1_1_0_,
user0_.created_at as created_2_1_0_,
user0_.email as email3_1_0_,
user0_.gender as gender4_1_0_,
user0_.name as name5_1_0_,
user0_.updated_at as updated_6_1_0_
from
user user0_
where
user0_.id=?
>>> postLoad
>>> preUpdate
Hibernate:
update
user
set
email=?,
gender=?,
name=?,
updated_at=?
where
id=?
>>> postUpdate
>>> preRemove
Hibernate:
delete
from
user
where
id=?
>>> postRemove
2. Event Listener를 왜 쓰는가?
- 일반적으로 Auditing하는 용도로 많이 사용
user.setCreatedAt()
,user.setUpdatedAt()
는 실수로 안넣을 확률이 높지만 중요한 데이터
→ 이런 실수와 반복적인 코드를 줄이기 위해 사용하는 경우가 많음
UserRepository.java (수정 전)
@Test
void prePersistTest(){
User user = new User();
user.setEmail("test@gmail.com");
user.setName("Kim");
user.setCreatedAt(LocalDateTime.now()); //현재 시간을 등록
user.setUpdatedAt(LocalDateTime.now()); //현재 시간을 등록
userRepository.save(user);
System.out.println(userRepository.findByEmail("test@gmail.com"));
}
User.java (수정 후)
public class User{
...
@PrePersist
public void prePersist(){
this.createdAt = LocalDateTime.now();
this.updatedAt = LocalDateTime.now();
}
...
}
UserRepository.java (수정 후)
@Test
void prePersistTest(){
User user = new User();
user.setEmail("test@gmail.com");
user.setName("Kim");
userRepository.save(user);
System.out.println(userRepository.findByEmail("test@gmail.com"));
}
실행결과 (수정 전, 수정 후 동일)
Hibernate:
call next value for hibernate_sequence
Hibernate:
insert
into
user
(created_at, email, gender, name, updated_at, id)
values
(?, ?, ?, ?, ?, ?)
binding parameter [1] as [TIMESTAMP] - [2021-07-23T22:41:58.879792900]
binding parameter [2] as [VARCHAR] - [test@gmail.com]
binding parameter [3] as [INTEGER] - [null]
binding parameter [4] as [VARCHAR] - [Kim]
binding parameter [5] as [TIMESTAMP] - [2021-07-23T22:41:58.879792900]
binding parameter [6] as [BIGINT] - [6]
3. 여러 객체에서 공통 Entity Listener 쓰는 법
- Entity 객체에
@EntityListeners
를 이용하여 공통화 가능
- 다형성 처리가 가능한 interface 생성 필요 (Auditable.java
)
- Listener 공통화 할 EntityListener 생성 (MyEntityListener.java
)
Auditable.java
public interface Auditable {
LocalDateTime getCreatedAt();
LocalDateTime getUpdatedAt();
void setCreatedAt(LocalDateTime createdAt);
void setUpdatedAt(LocalDateTime updatedAt);
}
MyEntityListener.java
public class MyEntityListener {
@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());
}
}
}
User.java
@Entity
@NoArgsConstructor
@Data
@EntityListeners(value = {MyEntityListener.class})
public class User implements Auditable{
...
@Column(updatable = false)
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
}
Book.java
@Entity
@NoArgsConstructor
@Data
@EntityListeners(value ={ MyEntityListener.class })
public class Book implements Auditable{
...
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
}
- 작업 전 Entity와 작업 후 Entity의 차이는
@PrePersist
,@PreUpdate
메소드가 각각의 Entity 안에 존재하지 않음
→MyEntityListener
안에 메소드들은 어떤 Entity를 받는지 알기 위해 Object 파라미터를 받음
→ Listener에선 Object가 Auditable을 상속받은 class인지 체크 후 실행
//MyEntityListener
@PrePersist
public void prePersist(Object o){ //Object로 해당 객체를 받음
if(o instanceof Auditable){
((Auditable) o).setCreatedAt(LocalDateTime.now());
((Auditable) o).setUpdatedAt(LocalDateTime.now());
}
}
//User.java
@PrePersist
public void prePersist(){ //this를 통해 해당 파라미터를 참조
this.createdAt = LocalDateTime.now();
this.updatedAt = LocalDateTime.now();
}
4. History 용도로 Entity Listener 쓰는 법
UserHistoryRepository.java 생성
public interface UserHistoryRepository extends JpaRepository<UserHistory, Long> {
}
UserEntityListener.java 생성
- jpa에 Listener는 @Component를 사용 못함, 즉 @Autowired를 할 수 없음
→ 그래서 BeanUtils를 통해 bean을 주입받아 사용 [ BeanUtils.getBean() ]
public class UserEntityListener {
@PrePersist
@PreUpdate
public void prePersistAndPreUpdate(Object o){
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);
}
}
BeanUtils.java 생성
@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
void userHistoryTest(){
User user = new User();
user.setEmail("test-new@gmail.com");
user.setName("Hong");
userRepository.save(user);
user.setName("Hong-new");
userRepository.save(user);
userHistoryRepository.findAll().forEach(System.out::println);
}
Hibernate:
call next value for hibernate_sequence
Hibernate:
call next value for hibernate_sequence
Hibernate:
insert
into
user_history
(created_at, email, name, updated_at, user_id, id)
values
(?, ?, ?, ?, ?, ?)
Hibernate:
insert
into
user
(created_at, email, gender, name, updated_at, id)
values
(?, ?, ?, ?, ?, ?)
Hibernate:
select
user0_.id as id1_2_0_,
user0_.created_at as created_2_2_0_,
user0_.email as email3_2_0_,
user0_.gender as gender4_2_0_,
user0_.name as name5_2_0_,
user0_.updated_at as updated_6_2_0_
from
user user0_
where
user0_.id=?
Hibernate:
call next value for hibernate_sequence
Hibernate:
insert
into
user_history
(created_at, email, name, updated_at, user_id, id)
values
(?, ?, ?, ?, ?, ?)
Hibernate:
update
user
set
email=?,
gender=?,
name=?,
updated_at=?
where
id=?
UserHistory(id=6, userId=null, name=Hong, email=test-new@gmail.com, createdAt=2021-07-24T08:58:16.331828, updatedAt=2021-07-24T08:58:16.331828)
UserHistory(id=8, userId=7, name=Hong-new, email=test-new@gmail.com, createdAt=2021-07-24T08:58:16.489824, updatedAt=2021-07-24T08:58:16.489824)
5. 일반적으로 많이 사용하는 Listener 사용법(날짜, 등록자)
MyEntityListener
처럼 등록날짜, 수정날짜, 등록자, 수정자는 많은 곳에서 사용 필요
- JPA, Spring Boot에선
@EnableJpaAuditing
,AuditingEntityListener.class
,annotation
을 통해 사용 가능
@CreatedDate
: 등록날짜,@LastModifiedDate
: 수정날짜
@CreatedBy
: 등록자,@LastModifiedBy
: 수정자
AuditingEntityListener
의 실행 결과는MyEntityListener
의 결과 동일
BookmanagerApplication.java
@SpringBootApplication
@EnableJpaAuditing
public class BookmanagerApplication {
public static void main(String[] args) {
SpringApplication.run(BookmanagerApplication.class, args);
}
}
User.java
...
@EntityListeners(value = {AuditingEntityListener.class, UserEntityListener.class})
public class User implements Auditable {
...
@Column(updatable = false)
@CreatedDate
private LocalDateTime createdAt;
@LastModifiedDate
private LocalDateTime updatedAt;
}
6. 코드 리팩터링 작업
createdAt
,updatedAt
은 여러 Entity에 공통으로 포함되어 있음
→BaseEntity
를 만들어 그 곳에서 관리하는 것으로 수정
@MappedSuperclass
: 상속받는 Entity에 컬럼으로 포함할 때 사용
@ToString(callSuper = true)
: 부모 Entity도 toString에 포함할 때 사용
@EqualsAndHashCode(callSuper = true)
: 부모 Entity도 동등 비교에 포함할 때 사용
BaseEntity.java
@Data
@MappedSuperclass
@EntityListeners(value = AuditingEntityListener.class)
public class BaseEntity {
@Column(updatable = false)
@CreatedDate
private LocalDateTime createdAt;
@LastModifiedDate
private LocalDateTime updatedAt;
}
User.java
...
@Data
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
@EntityListeners(value = {UserEntityListener.class})
public class User extends BaseEntity implements Auditable {
...
// @Column(updatable = false)
// @CreatedDate
// private LocalDateTime createdAt;
//
// @LastModifiedDate
// private LocalDateTime updatedAt;
...
}
Book.java
...
@Data
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class Book extends BaseEntity implements Auditable {
...
// @CreatedDate
// private LocalDateTime createdAt;
//
// @LastModifiedDate
// private LocalDateTime updatedAt;
}
UserHistory.java
@Data
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class UserHistory extends BaseEntity implements Auditable {
...
// @CreatedDate
// private LocalDateTime createdAt;
//
// @LastModifiedDate
// private LocalDateTime updatedAt;
}
실행 결과
Hibernate:
create table book (
id bigint not null,
created_at timestamp,
updated_at timestamp,
author varchar(255),
name varchar(255),
primary key (id)
)
...
//@ToString, @EqualsAndHashCode 선언 전
UserHistory(id=6, userId=null, name=Hong, email=test-new@gmail.com)
UserHistory(id=8, userId=7, name=Hong-new, email=test-new@gmail.com)
//@ToString, @EqualsAndHashCode 선언 후
UserHistory(super=BaseEntity(createdAt=2021-07-24T09:09:04.973194, updatedAt=2021-07-24T09:09:04.973194), id=6, userId=null, name=Hong, email=test-new@gmail.com)
UserHistory(super=BaseEntity(createdAt=2021-07-24T09:09:05.123189, updatedAt=2021-07-24T09:09:05.123189), id=8, userId=7, name=Hong-new, email=test-new@gmail.com)
'백엔드 > JPA' 카테고리의 다른 글
[ JPA ] 5-2. Entity Relations ( 1:1 @OneToOne ) (0) | 2021.07.25 |
---|---|
[ JPA ] 5-1. Entity Relations (ERD, 데이터베이스 기준 연관 관계) (0) | 2021.07.25 |
[ JPA ] 3. Entity 기본속성 (0) | 2021.07.22 |
[ JPA ] 2. Query Method 정의 및 실습 (0) | 2021.07.21 |
[ JPA ] 1. Repository interface 메소드 (1) | 2021.07.20 |