🚀 Spring Data JPA Auditing 심화편
– 현업에서는 이렇게까지 활용합니다
1. 실전 예제: createdBy, updatedBy를 로그인 사용자로 자동 세팅하기
AuditorAware를 구현할 때, 보통 로그인 사용자 정보를 가져와야 합니다.
스프링 시큐리티를 쓰는 경우 이렇게 작성합니다.
import org.springframework.data.domain.AuditorAware;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import java.util.Optional;
@Component
public class AuditorAwareImpl implements AuditorAware<String> {
@Override
public Optional<String> getCurrentAuditor() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null || !authentication.isAuthenticated()) {
return Optional.empty();
}
return Optional.of(authentication.getName()); // 보통 username 반환
}
}
포인트
- 로그인한 사용자가 없는 경우를 대비해 항상 Optional.empty()를 반환합니다.
- authentication.getName() 대신, 직접 UserDetails를 꺼내서 더 정교한 정보(예: 회원 ID)를 가져올 수도 있습니다.
2. DTO 변환 시 createdAt, updatedAt 같이 보내는 방법
많은 사람들이 엔티티에는 createdAt이 있는데,
DTO로 변환할 때 빼먹는 경우가 있습니다.
DTO로 응답할 때에도 createdAt, updatedAt을 명시적으로 노출하는 게 좋습니다.
public record PostResponse(
Long id,
String title,
String content,
LocalDateTime createdAt,
LocalDateTime updatedAt
) {
public static PostResponse from(Post post) {
return new PostResponse(
post.getId(),
post.getTitle(),
post.getContent(),
post.getCreatedAt(),
post.getUpdatedAt()
);
}
}
실무 팁
- 관리형 데이터에서는 createdAt 기준 정렬이 많기 때문에, 응답에도 넣어두는 것이 좋습니다.
- 특히 무한스크롤(페이징) 구현할 때 createdAt 기준으로 커서 페이징을 하는 경우가 많습니다.
3. MongoDB, Elasticsearch 등 NoSQL에도 Auditing 적용 가능
Spring Data JPA뿐만 아니라,
Spring Data MongoDB, Spring Data Elasticsearch 같은 NoSQL 계열도 Auditing을 지원합니다.
MongoDB 예시
@Document(collection = "posts")
public class Post {
@Id
private String id;
private String title;
private String content;
@CreatedDate
private LocalDateTime createdAt;
@LastModifiedDate
private LocalDateTime updatedAt;
}
주의할 점
- MongoDB에서는 @EnableMongoAuditing을 별도로 켜줘야 합니다.
- Elasticsearch에서는 @EnableElasticsearchAuditing을 켜줘야 합니다.
즉, DB 종류마다 @EnableXxxAuditing이 다릅니다.
(Spring Data JPA와 혼동 주의)
4. createdAt/updatedAt이 null로 나오는 경우 해결법
흔한 문제:
- createdAt이 null이거나
- updatedAt이 업데이트되어야 하는데 갱신되지 않는 경우
원인 체크리스트
- @EnableJpaAuditing을 선언했는가?
- @EntityListeners(AuditingEntityListener.class)가 BaseEntity에 붙어 있는가?
- 필드 타입이 LocalDateTime인가? (String이면 안 됨)
- save()가 호출되었는가? (flush 없이 트랜잭션 종료되면 반영 안 될 수 있음)
- 영속성 컨텍스트에 관리되고 있는 상태인가? (Detached 상태이면 안 먹음)
특히 5번 때문에, 영속성 컨텍스트(Managed 상태) 밖에서 엔티티를 변경하는 경우 Auditing이 작동하지 않는다는 것도 주의해야 합니다.
5. Auditing을 직접 커스터마이징할 수 있다
Spring Data JPA는 기본적으로 AuditingEntityListener를 사용하지만,
커스텀 리스너를 만들어서 더 다양한 이벤트(예: 삭제 시간 기록 등)를 처리할 수도 있습니다.
public class CustomAuditingEntityListener {
@PrePersist
public void onPrePersist(Object entity) {
if (entity instanceof AuditableEntity) {
((AuditableEntity) entity).setCreatedAt(LocalDateTime.now());
}
}
@PreUpdate
public void onPreUpdate(Object entity) {
if (entity instanceof AuditableEntity) {
((AuditableEntity) entity).setUpdatedAt(LocalDateTime.now());
}
}
}
- 이렇게 커스텀 EntityListener를 만들어
- @EntityListeners(CustomAuditingEntityListener.class)처럼 붙이면,
- 아주 세밀한 제어도 가능합니다.
실전 예시
- 논리 삭제(deletedAt) 처리할 때 사용하기도 합니다.
6. Bonus: soft delete와 Auditing 조합
현업에서는 물리 삭제 대신 soft delete(논리 삭제)를 쓰는 경우가 많습니다.
이 때, Auditing을 확장해서 관리할 수 있습니다.
- deletedAt
- deletedBy
BaseEntity를 이렇게 확장합니다.
@Getter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseSoftDeleteEntity {
@CreatedDate
private LocalDateTime createdAt;
@LastModifiedDate
private LocalDateTime updatedAt;
private LocalDateTime deletedAt;
private String deletedBy;
public void softDelete(String username) {
this.deletedAt = LocalDateTime.now();
this.deletedBy = username;
}
}
주의사항
- soft delete를 사용하면,
- Repository나 QueryDSL에서 deletedAt IS NULL 조건을 추가하는 걸 잊지 말아야 합니다.
✨ 최종 마무리 (심화편 요약)
주제 | 요약 |
로그인 사용자 기록 | AuditorAware로 구현 |
DTO 변환 시 createdAt 포함 | 커서 페이징 등에도 유용 |
MongoDB/ES에서도 Auditing 가능 | 단, @EnableXxxAuditing 필수 |
Auditing 작동 안할 때 | 영속성 상태, 설정 체크 |
커스텀 Auditing 리스너 가능 | deletedAt, deletedBy도 처리 가능 |
soft delete와 조합 | 실전 서비스에서는 필수 고려 |
'Dev Framework > Spring' 카테고리의 다른 글
[Spring][JPA] JPA QueryHint에 대해서 알아보자 (0) | 2025.05.01 |
---|---|
[Spring][JPA] N+1 문제의 발생부터 해결까지 (0) | 2025.05.01 |
[Spring][JPA] 스프링 데이터 JPA Auditing 완전 정복 (0) | 2025.04.28 |
[Spring][JPA] Hibernate에서 FROM 절 서브쿼리를 만들 수 없는 이유 (0) | 2025.04.28 |
[Spring] 트랜잭션 예외에 따른 커밋(Commit)과 롤백(Rollback) (0) | 2025.04.22 |