- Listener
이벤트가 발생하면 특정 동작을 진행
1. Entity Listener Annotation
<< Pre~ >>
@PrePersist - insert method가 호출되기 전
@PreUpdate - merge method가 호출되기 전
@PreRemove - delete method가 호출되기 전
<< Post~ >>
@PostPersist - insert method가 호출된 후
@PostUpdate - merge method가 호출된 후
@PostRemove - delete method가 호출된 후
@PostLoad - select 조회가 된 후
domain객체에 메소드 생성 ( * Annotaion명을 메소드명에 일치할 필요 없음 )
@NoArgsConstructor
@AllArgsConstructor
@RequiredArgsConstructor
@Data
@Builder
@Entity
@Table(name = "TB_USER", indexes = {@Index(columnList = "name")}, uniqueConstraints = {@UniqueConstraint(columnNames = "email")})
public class User{
@Id
@GeneratedValue
private Long id;
@NonNull
private String name;
@NonNull
private String email;
@Column(updatable = false)
private LocalDateTime createdAt;
@Column(insertable = false)
private LocalDateTime updatedAt;
// @OneToMany(fetch = FetchType.EAGER)
// private List<Address> address;
private Boolean useYn;
@Enumerated(value = EnumType.STRING)
private Gender gender;
@PrePersist
public void prePersist(){
System.out.println("PrePersist ::: ");
}
@PostPersist
public void postPersist(){
System.out.println("PostPersist ::: ");
}
@PreUpdate
public void preUpdate(){
System.out.println("PreUpdate ::: ");
}
@PostRemove
public void preRemove(){
System.out.println("PreRemove ::: ");
}
@PostLoad
public void postLoad(){
System.out.println("PostLoad ::: ");
}
}
Test
@Test
void entityListenerTest(){
User user = new User();
user.setEmail("poto@gmail.com");
user.setName("poto");
// insert 발생
userRepository.save(user);
User user2 = userRepository.findById(1L).get();
user2.setName("tomato");
// update 발생
userRepository.save(user2);
// delete 발생
userRepository.deleteById(4L);
// select 발생
userRepository.findAll().forEach(System.out::println);
}
PrePersist :::
Hibernate:
call next value for hibernate_sequence
Hibernate:
insert
into
tb_user
(created_at, email, gender, name, use_yn, 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_,
user0_.use_yn as use_yn7_1_0_
from
tb_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_,
user0_.use_yn as use_yn7_1_0_
from
tb_user user0_
where
user0_.id=?
PostLoad :::
PreUpdate :::
Hibernate:
update
tb_user
set
email=?,
gender=?,
name=?,
updated_at=?,
use_yn=?
where
id=?
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_,
user0_.use_yn as use_yn7_1_0_
from
tb_user user0_
where
user0_.id=?
PostLoad :::
Hibernate:
delete
from
tb_user
where
id=?
PreRemove :::
Hibernate:
select
user0_.id as id1_1_,
user0_.created_at as created_2_1_,
user0_.email as email3_1_,
user0_.gender as gender4_1_,
user0_.name as name5_1_,
user0_.updated_at as updated_6_1_,
user0_.use_yn as use_yn7_1_
from
tb_user user0_
PostLoad :::
PostLoad :::
PostLoad :::
PostLoad :::
PostLoad :::
User(id=1, name=tomato, email=martin@fastcampus.com, createdAt=2022-10-17T21:47:15.201159, updatedAt=2022-10-17T21:47:15.201159, useYn=true, gender=null)
User(id=2, name=dennis, email=dennis@fastcampus.com, createdAt=2022-10-17T21:47:15.202917, updatedAt=2022-10-17T21:47:15.202917, useYn=true, gender=null)
User(id=3, name=sophia, email=sophia@slowcampus.com, createdAt=2022-10-17T21:47:15.203719, updatedAt=2022-10-17T21:47:15.203719, useYn=true, gender=null)
User(id=5, name=martin, email=martin@another.com, createdAt=2022-10-17T21:47:15.205203, updatedAt=2022-10-17T21:47:15.205203, useYn=true, gender=null)
User(id=6, name=poto, email=poto@gmail.com, createdAt=null, updatedAt=null, useYn=null, gender=null)
Listener의 사용하는 경우
예를 들어 날짜 데이터와 같은 공통적인 속성으로 사용됨으로 setter()를 빼먹을 확률이 높아 이런 반복적인 코드를 줄이기 위해 Auditing하는 용도로 사용
Listener 적용 전
@Test
void auditingTest(){
User user = new User();
user.setEmail("poto@gmail.com");
user.setName("poto");
// User 정보를 새로 insert하는 과정에서 날짜 데이터의 반복적인 코드가 발생하게 된다
user.setCreatedAt(LocalDateTime.now());
user.setUpdatedAt(LocalDateTime.now());
userRepository.save(user);
System.out.println(userRepository.findByEmail("poto@gmail.com"));
}
Listener 적용 후
public class User {
...
@PrePersist
public void prePersist() {
this.createdAt = LocalDateTime.now();
this.updatedAt = LocalDateTime.now();
}
...
}
@Test
void auditingTest(){
User user = new User();
user.setEmail("poto@gmail.com");
user.setName("poto");
// User 정보를 새로 insert하는 과정에서 날짜 데이터의 반복적인 코드가 발생하게 된다
// @PrePersists 추가 후 생략 가능
//user.setCreatedAt(LocalDateTime.now());
//user.setUpdatedAt(LocalDateTime.now());
userRepository.save(user);
System.out.println(userRepository.findByEmail("poto@gmail.com"));
}
여러 객체에 공통 Entity Listener 사용
- Entity 객체에 @EntityListeners를 이용하여 공통화 가능
- 공통으로 사용하기 위해 다형성 처리가 가능한 interface 생성
- Listener 공통화할 EntityListener 생성
User.java ( @EntityListeners(value = ' 클래스명.class' )으로 지정 )
@NoArgsConstructor
@AllArgsConstructor
@RequiredArgsConstructor
@Data
@Builder
@Entity
@EntityListeners(value=CommonEntityListener.class)
@Table(name = "TB_USER")
//@Table(name = "TB_USER", indexes = {@Index(columnList = "name")}, uniqueConstraints = {@UniqueConstraint(columnNames = "email")})
public class User implements Auditable{
@Id
@GeneratedValue
private Long id;
@NonNull
private String name;
@NonNull
private String email;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
// @OneToMany(fetch = FetchType.EAGER)
// private List<Address> address;
private Boolean useYn;
@Enumerated(value = EnumType.STRING)
private Gender gender;
// @PrePersist
// public void prePersist(){
// this.createdAt = LocalDateTime.now();
// this.updatedAt = LocalDateTime.now();
// }
}
Book.java
@Entity
@NoArgsConstructor
@Data
@EntityListeners(value=CommonEntityListener.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 preUpdate(){
// this.updatedAt = LocalDateTime.now();
// }
}
Auditable 인터페이스
public interface Auditable {
// 인터페이스에 다형성 처리 가능한 메소드 생성
LocalDateTime getCreatedAt();
LocalDateTime getUpdatedAt();
void setCreatedAt(LocalDateTime createdAt);
void setUpdatedAt(LocalDateTime updatedAt);
}
공통 엔티티 리스너
public class CommonEntityListener {
@PrePersist
public void prePersist(Object o){ // Listener에서는 해당 엔티티를 받아야함
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());
}
}
}
@Test
void bookTest(){
Book b1 = new Book();
b1.setName("potato dream");
b1.setAuthor("potato");
bookRepository.save(b1);
System.out.println(bookRepository.findAll());
}
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=potato dream, author=potato, createdAt=2022-10-17T22:25:09.488193, updatedAt=2022-10-17T22:25:09.488225)]
History 용도로 사용하는 Entity Listener 사용 예
UserHistoryRepository.java
public interface UserHistoryRepository extends JpaRepository<UserHistory, Long> {
}
UserEntityListener.java
- Listener는 @Component를 사용하여 빈으로 등록이 안됨. @Autowired 필드 주입이 되지 않음
그래서 BeanUtils.java를 통해 빈을 주입받아 Listener 동작
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);
}
}
테스트
@Autowired
private UserRepository userRepository;
@Autowired
private UserHistoryRepository userHistoryRepository;
@Test
void histTest(){
User user = new User();
user.setEmail("hihi@gmail.com");
user.setName("hihi");
// insert
userRepository.save(user);
user.setName("hoho");
// update
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, updated_at, email, name, user_id, id)
values
(?, ?, ?, ?, ?, ?)
Hibernate:
insert
into
tb_user
(created_at, updated_at, email, gender, name, use_yn, 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_,
user0_.use_yn as use_yn7_2_0_
from
tb_user user0_
where
user0_.id=?
Hibernate:
call next value for hibernate_sequence
Hibernate:
insert
into
user_history
(created_at, updated_at, email, name, user_id, id)
values
(?, ?, ?, ?, ?, ?)
Hibernate:
update
tb_user
set
created_at=?,
updated_at=?,
email=?,
gender=?,
name=?,
use_yn=?
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(super=BaseEntity(createdAt=2022-10-18T21:54:41.531896, updatedAt=2022-10-18T21:54:41.531896), id=6, userId=null, name=hihi, email=hihi@gmail.com)
UserHistory(super=BaseEntity(createdAt=2022-10-18T21:54:41.615999, updatedAt=2022-10-18T21:54:41.615999), id=8, userId=7, name=hoho, email=hihi@gmail.com)
* createdAt과 updateAt의 경우 스프링에서 제공하는 Listener를 사용하여 BaseEntity를 만들어 공통으로 관리하는 것으로 수정하고 @ToString과 @EqualsAndHashCode를 선언하여 위와 같은 로그가 찍힘
Spring 기본 제공 Listener
- @CreatedDate, @LastModifiedDate 라는 Spring에서 기본적으로 제공하는 엔티티 리스너이며, 생성 시, 수정 시 해당 엔티티 소속성값에 날짜가 표현됨
- @MappedSuperClass 는 상속 받는 엔티티에 컬럼을 포함시킬 때 사용
- @ToString(callSuper = true) 의 경우 부모 엔티티도 toString에 포함시킬 때 사용
- @EqualsAndHashCode(callSuper = true)의 경우 부모 엔티티도 동등비교에 포함할 때 사용
- @ToString, @EqualsAndHashCode의 경우 User, Book 객체에 추가해준 이유는 @Entity? @Data? 가 오류가 발생하여 부모 엔티티에서 가져오는 BaseEntity 값들이 로그에 찍히지 않아 추가적으로 적용을 시켜줘야함
BaseEntity.java (여러 엔티티에서 공통으로 포함된 것을 관리)
@Data
@MappedSuperclass // 해당 클래스 필드를 상속받은 엔티티의 컬럼에 포함시킴
@EntityListeners(value= AuditingEntityListener.class) // 스프링 제공하는 기본 Listener 클래스 지정
public class BaseEntity {
@CreatedDate
private LocalDateTime createdAt;
@LastModifiedDate
private LocalDateTime updatedAt;
}
User.java
@NoArgsConstructor
@AllArgsConstructor
@RequiredArgsConstructor
@Data
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
@Builder
@Entity
//@EntityListeners(value={CommonEntityListener.class, UserEntityListener.class}) // EntityListener는 여러개 지정 가능
@EntityListeners(value={UserEntityListener.class}) // 스프링에서 제공하는 기본 리스너 클래스
@Table(name = "TB_USER")
//@Table(name = "TB_USER", indexes = {@Index(columnList = "name")}, uniqueConstraints = {@UniqueConstraint(columnNames = "email")})
public class User extends BaseEntity implements Auditable {
@Id
@GeneratedValue
private Long id;
@NonNull
private String name;
@NonNull
private String email;
// 스프링 제공되는 기본 리스너
// @CreatedDate
// private LocalDateTime createdAt;
// @LastModifiedDate
// private LocalDateTime updatedAt;
// @OneToMany(fetch = FetchType.EAGER)
// private List<Address> address;
private Boolean useYn;
@Enumerated(value = EnumType.STRING)
private Gender gender;
// @PrePersist
// public void prePersist(){
// this.createdAt = LocalDateTime.now();
// this.updatedAt = LocalDateTime.now();
// }
}
Book.java
@Entity
@NoArgsConstructor
@Data
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
//@EntityListeners(value=CommonEntityListener.class)
//@EntityListeners(value= AuditingEntityListener.class)
public class Book extends BaseEntity 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 preUpdate(){
// this.updatedAt = LocalDateTime.now();
// }
}
UserHistory.java
@Entity
@NoArgsConstructor
@Data
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
//@EntityListeners(value=CommonEntityListener.class)
//@EntityListeners(value= AuditingEntityListener.class)
public class UserHistory extends BaseEntity implements Auditable {
@Id
@GeneratedValue
private Long id;
private Long userId;
private String name;
private String email;
// @CreatedDate
// private LocalDateTime createdAt;
// @LastModifiedDate
// private LocalDateTime updatedAt;
}
* 테이블마다 created_at 과 updated_at 이 공통적으로 반복 추가된 것이 확인 됨
create table book (
id bigint not null,
created_at timestamp,
updated_at timestamp,
author varchar(255),
name varchar(255),
primary key (id)
)
Hibernate:
create table tb_addr (
id bigint not null,
primary key (id)
)
Hibernate:
create table tb_user (
id bigint not null,
created_at timestamp,
updated_at timestamp,
email varchar(255),
gender varchar(255),
name varchar(255),
use_yn boolean,
primary key (id)
)
Hibernate:
create table user_history (
id bigint not null,
created_at timestamp,
updated_at timestamp,
email varchar(255),
name varchar(255),
user_id bigint,
primary key (id)
)
'Spring > 1-3. JPA' 카테고리의 다른 글
(7. Spring Data JPA) Transaction Manager (0) | 2023.05.10 |
---|---|
(6. Spring Data JPA) 영속성 컨텍스트 Persistence Context (0) | 2023.05.06 |
(4. Spring Data JPA) Entity의 기본속성(@Entity) (0) | 2022.10.17 |
(3. Spring Data JPA)QueryMethod 쿼리메소드 정의 및 실습 (0) | 2022.10.15 |
(2. Spring Data JPA)JpaRepository의 findById와 getOne 차이 (0) | 2022.10.12 |