우당탕탕

JPA Auditing으로 생성·수정 시간 자동화하다가 겪은 삽질 후기 본문

Tech/Spring

JPA Auditing으로 생성·수정 시간 자동화하다가 겪은 삽질 후기

모찌모찝 2026. 6. 1. 22:28

제가 JPA Auditing 기능으로 엔티티의 생성 시간과 수정 시간을 자동으로 관리하려고 했는데, 생각보다 삽질이 엄청 많았어요. 처음에는 '그냥 @CreatedDate, @LastModifiedDate 붙이면 되겠지?' 했는데, 여러 번 반려도 당하고 에러도 나서 한참 헤맸거든요.

이번 글에서는 제가 직접 겪은 실패 사례, 놓친 부분들, 그리고 어떻게 해결했는지 차근차근 공유해볼게요. 이 글을 다 보면 JPA Auditing 적용 시 어떤 부분에서 막히는지, 그리고 어떻게 하면 매끄럽게 자동화를 끝낼 수 있는지 한눈에 이해하실 수 있을 거예요.

개발 환경 / 버전 정보

저는 Java 17 환경에서, Spring Boot 3.2 버전을 사용했어요. JPA는 Spring Data JPA를 썼고, Hibernate 6.x 버전이었습니다. 그리고 MySQL 8.x를 데이터베이스로 연결했어요.

JPA Auditing 핵심 구현 방법, 그리고 제가 실수했던 부분들

사실 JPA Auditing 기능 자체는 공식 문서만 보면 엄청 간단하거든요. 하지만 저는 아래 3가지 부분에서 크게 삽질했어요.

  • @EnableJpaAuditing 어노테이션 위치를 잘못 둬서 기능이 아예 동작하지 않았던 일
  • 엔티티에 @EntityListeners(AuditingEntityListener.class) 누락
  • 날짜 필드 타입과 포맷 불일치로 인해 시간 정보가 저장 안 된 문제

아래 코드는 제가 최종적으로 사용했던 JPA Auditing 적용 예시입니다.

@Entity
@EntityListeners(AuditingEntityListener.class)
public class Post {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String title;

    @CreatedDate
    @Column(updatable = false)
    private LocalDateTime createdAt;

    @LastModifiedDate
    private LocalDateTime updatedAt;

    // ...생성자, 게터 세터 생략...
}

여기에서 핵심은 @CreatedDate와 @LastModifiedDate를 사용할 때 꼭 등록된 Entity Listener가 있어야 한다는 점이에요. 보통 @EntityListeners(AuditingEntityListener.class)를 엔티티 클래스에 붙여줘야 하죠.

그리고, 이 기능을 쓰려면 스프링 부트 메인 클래스나 @Configuration 클래스에 @EnableJpaAuditing 어노테이션을 꼭 선언해야 하는데, 처음에 이걸 놓쳐서 아무 에러도 안 나고 단순히 날짜 필드가 전혀 세팅이 안 됐어요.

@EnableJpaAuditing 적용 위치 때문에 헤맨 경험

저는 처음에 @EnableJpaAuditing을 별도 설정 클래스에만 붙였는데, 스캔이 제대로 안 됐는지 동작하지 않았어요. 그래서 반복해서 테이블에 createdAt, updatedAt 칼럼이 안 채워지고, 심지어는 버그인지 의심했죠.

결국에 프로젝트 메인 클래스에 아래처럼 붙였더니 바로 작동하더라고요.

@SpringBootApplication
@EnableJpaAuditing
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}

혹시 설정 클래스를 분리해서 쓰더라도 스캔 범위에 포함되는 위치에 두거나, 컴포넌트 스캔 범위 내에 있어야 합니다. 저처럼 이 부분 간과해서 한참 고생하시는 분 많더라고요.

날짜 타입 선택과 저장 문제로 겪은 삽질

JPA Auditing에서 자동으로 들어가는 날짜 타입 필드는 보통 java.time.LocalDateTime이나 java.util.Date를 쓰는데, 저는 처음에 java.util.Date 타입으로만 쓰다가 데이터베이스에 1970-01-01 같은 이상한 날짜가 찍혔어요.

한참 헤매다가 Hibernate 버전 업하면서 java.time.LocalDateTime 타입을 쓰고, 엔티티 필드에 별도로 @Column(updatable = false) 조건을 주니 제대로 시간 정보가 저장되더라고요.

이 부분을 몰라서 여러 번 테스트 테이블을 날리고, 롤백하고 다시 시도한 게 꽤 아까웠습니다.

저는 이렇게 해결했어요 – 최소한의 설정으로 제대로 동작하게 하기

정리하면, 다음 세 가지만 잘 챙기면 JPA Auditing은 문제 없이 동작합니다.

  • @EnableJpaAuditing 어노테이션을 반드시 스프링 부트 메인 클래스나 컴포넌트 스캔 범위 안에 선언
  • @EntityListeners(AuditingEntityListener.class)를 엔티티에 붙이기
  • 생성/수정 시간 필드 타입은 LocalDateTime으로 쓰고, 필요하면 @Column(updatable = false) 같은 옵션으로 변경 방지 설정 추가

특히 저는 첫 번째랑 두 번째를 빼먹어서 진짜 여러 번 같은 실수로 반려됐거든요. 그래서 꼭 기억하시라고 강조합니다.

여기서 많이 틀리는 질문

Q. @EnableJpaAuditing이 있어도 생성일이 안 들어가요. 이유가 뭔가요?

A. 보통 @EntityListeners(AuditingEntityListener.class)가 누락된 경우가 많아요. 그리고 필드에 @CreatedDate가 안 붙었거나, 엔티티가 제대로 스프링 컨텍스트에서 관리되지 않는 경우도 있습니다.

Q. LocalDateTime 대신 java.util.Date 써도 되나요?

A. 가능은 한데, 저는 예전 버전과 Hibernate 업그레이드 과정에서 의도치 않은 시간 손실이나 이상한 값이 찍히는 문제를 겪어서 추천하지 않아요. LocalDateTime이 대부분 최신 프로젝트에 더 안정적입니다.

심화: 더 편리하게 쓰고 싶다면

저는 나중에 공통 속성을 모두 담는 BaseEntity 클래스를 만들어서, 생성 시간과 수정 시간 필드를 거기에 두고 모든 엔티티에 상속시키는 방식을 썼어요. 이렇게 하면 반복 코드도 줄고 관리가 훨씬 편하더라고요.

@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseEntity {

    @CreatedDate
    @Column(updatable = false)
    private LocalDateTime createdAt;

    @LastModifiedDate
    private LocalDateTime updatedAt;

    // 게터만 공개
    public LocalDateTime getCreatedAt() {
        return createdAt;
    }

    public LocalDateTime getUpdatedAt() {
        return updatedAt;
    }
}

실제로 이렇게 구성하니 새로운 엔티티 만들 때마다 시간 관련 필드를 붙일 필요가 없어지고, Auditing도 자연스럽게 다 적용되어서 생산성이 훨씬 올랐어요.

그리고 혹시 JSON 직렬화할 때 시간 포맷 문제 생기면, @JsonFormat 어노테이션이나 Jackson 설정으로 쉽게 맞춰줄 수 있으니 참고하세요.

저도 처음에는 Auditing 관련 설정 하나하나 귀찮았지만, 이렇게 제대로 잡아놓으니까 업무 효율이 확실히 좋아지더라고요.

결론적으로 JPA Auditing 적용할 때는 설정 위치, EntityListener 등록, 날짜 타입 이 세 가지만 확실히 챙기면 삽질 없이 쓸 수 있습니다. 저처럼 삽질 반복하지 마시고 이 글 참고하시면 좋겠네요!

Comments