Entity Listener의 활용 (AuditingEntityListener)
Listener : 이벤트를 관찰하고 있다가 이벤트가 발생하면 콜백 메서드를 통해 특정 동작을 수행한다.
JPA에서는 아래의 7가지 콜백 메서드를 제공한다.
@PrePersist : insert 메소드 수행 전 시행
@PreUpdate : merge 메소드 호출 전 시행
@PreRemove : delete 메소드 호출 전 시행
@PostPersist : insert 메소드 수행 후 시행
@PostUpdate : merge 메소드 수행 후 시행
@PostRemove : delete 메소드 수행 후 시행
@PostLoad : select 조회가 일어난 직후에 실행
보통 @PrePersist와 @PreUpdate를 많이 사용한다. 특히 auditing(감시)할 때 사용한다.
예시
엔티티의 생성 시간(createdAt)과 수정 시간(updatedAt)을 설정할 때, setter를 이용하여 날짜 데이터를 입력할 수 있다.
그러나 단순한 날짜 데이터를 등록 / 수정하는 코드가 반복되기 때문에
Listener를 사용함으로써 DRY(Dont Repeat yourself) 원칙에 따라 중복을 최소화할 수 있고 코드의 재사용성을 높일 수 있다.
기존 (setter)
User user = new User();
user.setEmail("han@abc.com");
user.setName("han");
user.setCreatedAt(LocalDateTime.now());
user.setUpdatedAt(LocalDateTime.now());
보완 (Listener)
@Entity
@EntityListeners(value = AuditingEntityListener.class)
public class User {
...
@Column(updatable = false)
@CreatedDate // 자동으로 시간 값을 지정
private LocalDateTime createdAt;
@Column
@LastModifiedDate // 자동으로 시간 값을 지정
private LocalDateTime updatedAt;
}
Spring에서 제공해주는 기본 EntityListener인 AuditingEntityListener 클래스를 이용한다.
생성 시간에는 @CreatedDate, 수정 시간에는 @LastModifiedDate 어노테이션을 붙여준다.
(생성과 수정에 누가 관여했는지 보여주는 @CreatedBy, @LastModifiedBy 또한 존재한다.)
@SpringBootApplication
@EnableJpaAuditing // Auditing 활성화
public class BookmanagerApplication {
public static void main(String[] args) {
SpringApplication.run(BookmanagerApplication.class, args);
}
}
Main 클래스에 @EnableJpaAuditing을 사용하면 활성화된다.
결과
@Test
void userHistoryTest(){
User user = new User();
user.setEmail("han@abc.com");
user.setName("han");
// insert
userRepository.save(user);
// update
user.setName("New Han");
userRepository.save(user);
userHistoryRepository.findAll().forEach(System.out::println);
}
엔티티가 생성되거나 수정될 때 setter를 이용해 반복해서 수행하지 않고 AuditingEntityListener를 이용하여 자동으로 시간이 설정되게 할 수 있다.
*추가 보완*
여러 엔티티에 @CreatedDate, @LastModifiedDate를 각각 다는 것 또한 중복되기 때문에,
1. 기본 엔티티를 만든다.
2. 기본 엔티티에 엔티티리스너를 연결한다.
3. 각 엔티티에서 기본 엔티티를 상속 받는다.
위와 같은 방식을 진행하는 것을 추천한다.
@Data
@MappedSuperclass // 해당 클래스의 필드를 상속받는 엔티티의 컬럼으로 포함시키는 어노테이션
@EntityListeners(value = AuditingEntityListener.class)
public class BaseEntity {
@CreatedDate
private LocalDateTime createdAt;
@LastModifiedDate
private LocalDateTime updatedAt;
}
기본 엔티티(BaseEntity)를 만들고 엔티티리스너(AuditingEntityListener)를 연결한다.
각 엔티티에서의 createdAt과 updatedAt이 중복되는 것을 막기 위해 기본 엔티티에 시간을 추가하고,
@MappedSuperclass를 사용하여 해당 클래스의 필드를 상속받는 엔티티의 컬럼으로 포함시킨다.
@Entity
@NoArgsConstructor
@Data
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
//@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;
}
BaseEntity를 상속받기 때문에 @EntityListener와 createdAt 그리고 updatedAt은 사용하지 않는다.
또한 상속 받을 때 @Data의 에러가 발생한다.
(이 클래스가 java.lang.Object를 확장하지 않은 경우에도 상위 클래스를 호출하지 않고 equals/hashCode 구현을 생성합니다. 의도적인 경우에는 타입에 '(call Super=false)'를 추가하세요. )
따라서 @ToString과 @EqualsAndHashcode 어노테이션에 (callSuper = true)라는 조건을 설정하여 상속을 통한 에러를 처리한다.
결과