Listener
: 어떠한 이벤트가 발생하면 호출되어 특정 동작을 수행
<method 실행 전>
@PrePersist
: insert method가 호출되기 전
@PreUpdate
: merge method가 호출되기 전
@PreRemove
: delete method가 호출되기 전
<method 실행 이후>
@PostPersist
: insert method가 호출된 이후
@PostUpdate
: merge method가 호출된 이후
@PostRemove
: delete method가 호출된 이후
@PostLoad
: select 조회가 된 직후
메서드 생성(User.java)
@NoArgsConstructor
@AllArgsConstructor
@RequiredArgsConstructor
@Data
@Builder
@Entity
@Table(name="user")
public class User {
...
@PrePersist
public void prePersist(){
System.out.println(">>> prePersist");
}
@PostPersist
public void postPersist(){
System.out.println(">>> postPersist");
}
...
}
test 코드
@Test
void listenerTest(){
User user = new User();
user.setEmail("martin@naver.com");
user.setName("martin");
userRepository.save(user);
User user2 = userRepository.findById(1L).orElseThrow(RuntimeException::new);
user2.setName("marrrrtin");
userRepository.save(user2);
userRepository.deleteById(4L);
}
>>> perPersist
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
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
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
>>> PreRemove
Hibernate:
delete
from
user
where
id=?
>>> PostRemove
사용 이유 예시(Jpa Auditing)
public class User{
...
@PrePersist
public void prePersist(){
this.createdAt = LocalDateTime.now();
this.updatedAt = LocalDateTime.now();
}
@PreUpdate
public void PreUpdate(){
this.updatedAt = LocalDateTime.now();
}
...
}
위 경우 insert,update 쿼리 실행 전 각 변수에 값이 들어감
Entity 객체에 @EntityListeners
를 이용하여 공통화 가능
Auditable.java
public interface Auditable {
LocalDateTime getCreatedAt();
LocalDateTime getUpdatedAt();
void setCreatedAt(LocalDateTime createdAt);
void setUpdatedAt(LocalDateTime updatedAt);
}
MyEntityListener
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
@Entity
@NoArgsConstructor
@Data
@EntityListeners(value = {MyEntityListener.class})
public class User implements Auditable{
...
@Column(updatable = false)
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
}
Book
@Entity
@NoArgsConstructor
@Data
@EntityListeners(value ={ MyEntityListener.class })
public class Book implements Auditable{
...
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
}
MyEntityListener 안에 메소드들은 어떤 Entity를 받는지 알기 위해 Object 파라미터를 받음
→ Listener에선 Object가 Auditable을 상속받은 class인지 체크 후 실행
보통 update로 인한 수정이 일어나기 전 데이터를 저장하기 위해 사용
UserHistory
@Entity
@NoArgsConstructor
@Data
@EntityListeners(value = MyEntityListener.class)
public class UserHistory implements Auditable{
@Id
@GeneratedValue
private Long id;
private Long userId;
private String name;
private String email;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
}
public interface UserHistoryRepository extends JpaRepository<UserHistory, Long> {
}
public class UserEntityListener {
@PrePersist
@PreUpdate
public void prePersistAndUpdate(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);
}
}
jpa에 Listener는 @Component를 사용 못함, 즉 @Autowired를 할 수 없음
→ 그래서 BeanUtils를 통해 bean을 주입받아 사용
Bean Utils
@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("martin-new@naver.com");
user.setName("martin-new");
userRepository.save(user);
user.setName("martin-new-new");
userRepository.save(user);
userHistoryRepository.findAll().forEach(System.out::println);
}
UserHistory(id=6, userId=null, name=martin-new, email=martin-new@naver.com, createdAt=2022-11-19T18:19:43.775190, updatedAt=2022-11-19T18:19:43.775197)
UserHistory(id=8, userId=7, name=martin-new-new, email=martin-new@naver.com, createdAt=2022-11-19T18:19:43.796948, updatedAt=2022-11-19T18:19:43.796952)
MyEntityListener처럼 등록날짜, 수정날짜, 등록자, 수정자는 많은 곳에서 사용 필요
JPA, Spring Boot에선 @EnableJpaAuditing
, AuditingEntityListener.class
, annotation
을 통해 사용 가능
@CreatedDate: 등록날짜, @LastModifiedDate: 수정날짜
@CreatedBy: 등록자, @LastModifiedBy: 수정자
AuditingEntityListener의 실행 결과는 MyEntityListener의 결과 동일
@SpringBootApplication
@EnableJpaAuditing
public class BookmanagerApplication {
public static void main(String[] args) {
SpringApplication.run(BookmanagerApplication.class, args);
}
}
User
@EntityListeners(value = {AuditingEntityListener.class, UserEntityListener.class})
public class User implements Auditable {
...
@Column(updatable = false)
@CreatedDate
private LocalDateTime createdAt;
@LastModifiedDate
private LocalDateTime updatedAt;
}
createdAt
, updatedAt
은 여러 Entity에 공통으로 포함되어 있음
→ BaseEntity를 만들어 그 곳에서 관리하는 것으로 수정
@MappedSuperclass
: 상속받는 Entity에 컬럼으로 포함할 때 사용
@ToString(callSuper = true)
: 부모 Entity도 toString에 포함할 때 사용
@EqualsAndHashCode(callSuper = true)
: 부모 Entity도 동등 비교에 포함할 때 사용
BaseEntity
@Data
@MappedSuperclass
@EntityListeners(value = AuditingEntityListener.class)
public class BaseEntity {
@Column(updatable = false)
@CreatedDate
private LocalDateTime createdAt;
@LastModifiedDate
private LocalDateTime updatedAt;
}
User,Book,UserHistory에는 @ToString(callSuper = true) 와 @EqualsAndHashCode(callSuper=true) 지정 및 BaseEntity 상속 받음
//@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)