개발을 하다보면 공통적으로 도메인들이 가지고 있는 필드나 컬럼들이 존재할 것이다. 대표적으로 생성일자, 수정일자, 식별자 같은 컬럼이 있을 것이다.
도메인마다 공통으로 존재한다는 의미는 결국 코드가 중복된다는 말과 동일하다.
중복을 제거하고 비지니스에 집중하도록 하는 것이 개발자의 숙명이 아니겠는가.
따라서, JPA Auditing는 Entity의 변경 이력 관리에 필요한 기본적인 기능을 자동화하여 코드의 간결함과 유지보수성을 높이는 데 기여한다. 이를 통해 엔티티 생성과 수정 작업에 대해 일관성 있고 신뢰할 수 있는 이력을 관리할 수 있다.
@EnableJpaAuditing
@SpringBootApplication
public class MainDevApplication {
public static void main(String[] args) {
SpringApplication.run(MainDevApplication.class, args);
}
}
Entity마다 중복되는 소스이기에 별도의 클래스로 작성하고 다른 Entity에서 상속받아 사용하도록 한다.
@Getter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public class BaseEntity {
@CreatedDate
@Column(updatable = false)
private LocalDateTime fstEnrDtm;
@LastModifiedDate
private LocalDateTime lstChgDtm;
}
@EntityListeners
@MappedSuperclass
@CreateDate
@LastModifiedDate
이렇게 적용하고 테스트해 보면 기본적인 JPA Auditing 기능은 구현되었다.
생성시간, 수정시간 외에 생성자, 수정자에 대한 정보를 설정하려면 AuditorAware 구현체를 만들어야 한다.
@CreatedDate, @LastModifiedDate는 구현체 없이 사용할 수 있지만 @CreatedBy, @LastModifiedBy는 구현체가 필요하다.
public class AuditorAwareImpl implements AuditorAware<String> {
@Override
public Optional<String> getCurrentAuditor() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication.getPrincipal().equals("anonymousUser")) {
return Optional.of("anoymousUser");
}
PrincipalDetails principalDetails = (PrincipalDetails) authentication.getPrincipal();
return Optional.of(String.valueOf(principalDetails.getUserId()));
}
}
JWT 인증을 구현해 놓은 환경에서 진행 중이기 때문에 변경하는 USER의 정보를 Security Context에서 가지고 올 수 있다.
이 부분은 각자 상황에 맞게 구현하면 된다.
@Configuration
@EnableJpaAuditing
public class JpaConfiguration {
@Bean
public AuditorAware<String> auditorProvider() {
return new AuditorAwareImpl();
}
}
@Setter
@Getter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public class BaseEntity {
@CreatedDate
@Column(updatable = false)
private LocalDateTime fstEnrDtm;
@CreatedBy
@Column(updatable = false)
private String fstEnrUser;
@LastModifiedDate
private LocalDateTime lstChgDtm;
@LastModifiedBy
private String lstChgUser;
}
@CreatedBy
@LastModifiedBy
위 설정까지 진행한 후 JPA를 사용해 INSERT, UPDATE를 해보면 @CreatedBy와 @LastModifiedBy설정이 추가된 필드에 자동으로 값이 설정되는 것을 확인할 수 있다.
이제는 기본 AuditingEntityListener대신 Custom EntityListener를 구현하여 사용해 보자.
CustomEntityListener를 구현하고 생성시간, 수정시간, 생성자, 수정자 이외에 IP, URI경로 등 정보도 자동으로 설정해 보자.
@Component
@RequiredArgsConstructor
public class CustomEntityListener{
private final RequestHeader requestHeader;
@PrePersist
public void prePersists(Object entity) {
if(entity instanceof BaseEntity) {
((BaseEntity) entity).setFstEnrUser(requestHeader.getUserId());
((BaseEntity) entity).setLstChgUser(requestHeader.getUserId());
((BaseEntity) entity).setFstEnrDtm(LocalDateTime.now());
((BaseEntity) entity).setLstChgDtm(LocalDateTime.now());
((BaseEntity) entity).setFstEnrUrl(requestHeader.getUrlPath());
((BaseEntity) entity).setLstChgUrl(requestHeader.getUrlPath());
}
}
@PreUpdate
public void preUpdate(Object entity) {
if(entity instanceof BaseEntity) {
((BaseEntity) entity).setLstChgUser(requestHeader.getUserId());
((BaseEntity) entity).setLstChgDtm(LocalDateTime.now());
((BaseEntity) entity).setLstChgUrlPath(requestHeader.getUrlPath());
}
}
...
}
Custom EntityListener를 생성하여 정보를 설정하려면 Request 한 User의 정보나 URI 경로 등을 알아야 한다.
코드에 나와있는 RequestHeader는 JWT인증 시 User의 정보와 URI경로 등의 정보를 가지고 있다.
이 부분은 각자 상황에 맞게 구현하자.
테스트를 위함이라면 하드코딩하고 테스트를 진행해도 된다.
@Setter
@Getter
@MappedSuperclass
//@EntityListeners(AuditingEntityListener.class)
@EntityListeners(CustomEntityListener.class)
public class BaseEntity {
@JsonFormat(pattern = "yyyyMMddHHmmss")
@CreatedDate
@Column(updatable = false)
private LocalDateTime fstEnrDtm;
//@CreatedBy AuditingEntityListener에서 사용
@Column(updatable = false)
private String fstEnrUser;
@Column(updatable = false)
private String fstEnrUrl;
@JsonFormat(pattern = "yyyyMMddHHmmss")
@LastModifiedDate
private LocalDateTime lstChgDtm;
//@LastModifiedBy AuditingEntityListener에서 사용
private String lstChgUser;
private String lstChgUrl;
}
EntityListener를 통해 엔티티의 생명주기 이벤트를 감지하고, 원하는 로직을 자동으로 실행할 수 있다.
이를 활용하면 데이터 변경 시 감사 로깅, 데이터 무결성 유지, 특정 조건에 따른 추가 작업 등을 쉽게 구현할 수 있다.
JPA 영속성 컨텍스트 이해 (Persistence Context) (14) | 2024.08.26 |
---|---|
[Spring] JPA Example (JpaRepository) (12) | 2023.03.30 |
ORM, JPA에 대한 이해 (12) | 2023.03.29 |
댓글 영역