JPA Auditing으로 생성일과 수정일을 일관되게 관리하기
게시글, 댓글, 회원, 주문처럼 대부분의 테이블에는 생성 시각과 마지막 수정 시각이 필요하다. 각 엔티티에 날짜 필드와 저장 코드를 반복해서 작성하면 빠뜨리기 쉽고, 컬럼 이름과 시간대도 제각각이 된다. Spring Data JPA의 Auditing 기능은 엔티티의 생명주기에 맞춰 이 값을 채워 준다.
공통 베이스 엔티티 만들기
공통 필드를 MappedSuperclass에 두면 이를 상속한 엔티티가 같은 컬럼을 갖는다. CreatedDate는 최초 저장 시각을, LastModifiedDate는 변경 시각을 기록한다.
@Getter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class Timestamped {
@CreatedDate
@Column(nullable = false, updatable = false)
private LocalDateTime createdAt;
@LastModifiedDate
@Column(nullable = false)
private LocalDateTime modifiedAt;
}
createdAt에는 updatable = false를 지정해 수정 시각이 생성 시각을 덮어쓰지 않도록 한다. 애플리케이션이 사용하는 날짜 타입은 LocalDateTime처럼 시간대가 없는 값인지, Instant처럼 UTC 기준 값인지 팀 규칙을 먼저 정한다. 여러 지역의 사용자가 있는 서비스라면 저장은 UTC, 표시만 사용자 시간대로 변환하는 방식이 안전하다.
Auditing 활성화와 적용 범위
애플리케이션 설정에 EnableJpaAuditing을 추가하고, 시간 필드가 필요한 엔티티만 베이스 클래스를 상속한다.
@SpringBootApplication
@EnableJpaAuditing
public class BlogApplication {
}
@Entity
public class Article extends Timestamped {
private String title;
}
모든 테이블이 생성·수정 시각을 필요로 하는 것은 아니다. 로그성 테이블이나 외부 시스템의 원본 시각을 보존해야 하는 테이블에는 자동 값을 무심코 적용하지 않는다. 감사 주체까지 기록해야 한다면 CreatedBy, LastModifiedBy와 AuditorAware를 함께 구성한다.
테스트에서 확인할 것
Auditing은 저장소 테스트로 실제 동작을 확인하는 편이 좋다. 엔티티를 저장한 뒤 생성 시각이 채워지고, 다시 수정한 뒤 수정 시각이 더 늦어졌는지 확인한다. 테스트가 너무 빠르면 두 시간이 같아 보일 수 있으므로 modifiedAt이 createdAt보다 늦거나 같은지처럼 허용 범위를 정한다.
Article article = articleRepository.save(new Article("초안"));
entityManager.flush();
entityManager.clear();
Article saved = articleRepository.findById(article.getId()).orElseThrow();
assertThat(saved.getCreatedAt()).isNotNull();
assertThat(saved.getModifiedAt()).isNotNull();
벌크 JPQL이나 네이티브 SQL로 값을 바꾸면 JPA 이벤트가 실행되지 않아 modifiedAt이 자동으로 바뀌지 않는다. 이런 작업은 SQL에서 직접 시간을 갱신하거나, 작업 후 별도 감사 기록을 남기는 기준이 필요하다.
날짜 필드가 알려 주는 것과 알려 주지 않는 것
createdAt과 modifiedAt은 마지막 상태를 알리는 값이지, 누가 어떤 필드를 왜 바꿨는지 알려 주는 이력은 아니다. 변경 이력이 필요하면 별도 감사 테이블이나 이벤트 로그를 설계해야 한다. 반대로 단순 정렬과 캐시 무효화가 목적이라면 공통 시간 필드만으로 충분할 수 있다.
다음 항목을 정하고 적용하면 Auditing이 단순한 편의 기능을 넘어 일관된 데이터 계약이 된다.
- 저장 시간의 기준 시간대는 무엇인가?
- 생성 시각을 수정할 수 없도록 DB 제약도 둘 것인가?
- 삭제 시각과 삭제 주체를 같은 베이스 클래스에 포함할 것인가?
- 벌크 작업과 외부 동기화에서 수정 시각을 어떻게 기록할 것인가?
- 사용자 화면에 시간대를 어떻게 표시할 것인가?
반복 코드를 줄이는 것보다 더 중요한 것은 모든 엔티티가 같은 의미의 시간을 갖게 하는 것이다. 컬럼 이름, 시간대, 테스트 규칙을 함께 정하면 목록 정렬과 캐시·검색 기준도 흔들리지 않는다.
함께 읽기:
원문 기록: Spring Day 9: JPA Auditing