ABOUT

성능과 운영 안정성을 함께 끌어올리는 개발자입니다.

92% Positional Error Reduction
79% p95 Latency Improvement
90%+ Long Tasks Reduction

2022.02 · 한국장학재단

우수 멘티

한국장학재단 사회 리더 대학생 멘토링 IT

2022.10 · 동작구청

우수 인재상

동작구청 우수 SW 인재

2025.05 · (주) 그랩

프로그래밍 우수상

(주) 그랩 우수 프로그램 개발

2025.05 · AWSKRUG

AWS한국사용자모임 발표

AI agent 스크립트 튜닝 관련 발표

ComputerScience

Development

Engineering

Trouble Shooting

GUESTBOOK

첫 마음부터
함께 나누는 온기

방명록 작성하러 가기

SUBSCRIBE

최신소식을
편하게 만나보세요.

@EntityListeners(AuditingEntityListener.class)

도입

엔티티 저장·수정 시점마다 생성일, 수정일, 생성자, 수정자 같은 감사 메타데이터를 자동으로 채우게 만드는 데 있다

JPA 엔티티를 다루다 보면 거의 모든 테이블에 비슷한 칼럼이 반복됩니다. 대표적으로 created_at, updated_at, created_by, updated_by 같은 필드들입니다.

이 값을 서비스 코드에서 매번 수동으로 세팅하면 금방 중복이 생기고, 일부 엔티티에서는 누락되기 쉽습니다. Spring Data JPA의 auditing 기능은 바로 이 반복을 줄이기 위해 존재합니다. 그 중심에 있는 것이 AuditingEntityListener이고, 엔티티에 이 리스너를 연결하는 가장 흔한 표현이 바로 @EntityListeners(AuditingEntityListener.class)입니다.

필요성

auditing 리스너를 이해하면 반복적인 생성일·수정일·작성자 세팅 코드를 줄일 수 있고, 감사 메타데이터를 서비스 코드가 아니라 persistence 계층 규칙으로 일관되게 관리할 수 있다

감사 메타데이터를 서비스 코드에서 직접 채우면 중복이 빠르게 늘어납니다. 신규 엔티티에서는 누락되기 쉽고, 수정 로직이 여러 서비스에 흩어져 있으면 규칙도 금방 어긋납니다.

반면 auditing 리스너를 쓰면 “엔티티가 저장되거나 수정될 때 감사 필드를 채운다”는 규칙을 persistence 계층으로 끌어내릴 수 있습니다. 이 덕분에 비즈니스 로직은 핵심 도메인 규칙에 더 집중하고, 감사 정보는 공통 인프라로 다룰 수 있습니다.

이 기능이 특히 유용한 경우
  • 모든 테이블에 생성일/수정일 칼럼이 거의 공통으로 들어가는 경우
  • 작성자와 수정자를 보안 컨텍스트에서 자동 추출하고 싶은 경우
  • BaseEntity 패턴으로 공통 필드를 묶어 재사용하려는 경우
  • 감사 메타데이터를 서비스 코드가 아니라 JPA 계층에서 일관되게 처리하고 싶은 경우

정의

`@EntityListeners(AuditingEntityListener.class)`는 해당 엔티티 또는 매핑 슈퍼클래스에 Spring Data JPA의 감사용 JPA entity listener를 연결하는 선언이며, 이 리스너는 저장과 수정 시점의 감사 메타데이터를 채우는 역할을 한다

먼저 `@EntityListeners`는 JPA 표준 어노테이션입니다. Jakarta Persistence 문서는 이 어노테이션이 엔티티 클래스나 매핑 슈퍼클래스에 적용될 수 있고, 콜백 리스너 클래스를 지정한다고 설명합니다.

여기에 Spring Data JPA가 제공하는 `AuditingEntityListener`를 연결하면, 해당 엔티티는 감사 메타데이터를 자동 채울 수 있는 대상이 됩니다. `AuditingEntityListener` Javadoc도 이 클래스를 “persisting and updating entities” 시점의 auditing 정보를 포착하는 JPA entity listener라고 설명합니다.

핵심 문장
@EntityListeners(AuditingEntityListener.class)는 “이 엔티티의 생명주기 이벤트에 감사 메타데이터 자동 채우기 로직을 붙인다”는 선언으로 이해하면 가장 정확합니다.

핵심 원리

이 어노테이션의 진짜 의미는 “엔티티가 저장·수정될 때마다 필요한 감사 필드를 프레임워크가 대신 채우게 만든다”는 데 있으며, 서비스 코드에서 반복하던 책임을 JPA 리스너로 이동시키는 것이다

Spring Data JPA auditing 문서는 `@CreatedBy`, `@LastModifiedBy`, `@CreatedDate`, `@LastModifiedDate`를 사용해 누가 만들었는지, 누가 수정했는지, 언제 변경되었는지를 기록할 수 있다고 설명합니다.

즉, 이 리스너를 붙인다고 해서 magically 모든 게 끝나는 것은 아니고, 엔티티에 어떤 감사 메타데이터를 둘지 먼저 선언해야 합니다. 리스너는 그 선언된 필드들을 적절한 시점에 채워 주는 실행 엔진에 가깝습니다.

기본 구조

Spring Data JPA auditing을 제대로 이해하려면 엔티티 리스너, 감사 어노테이션, `@EnableJpaAuditing`, `AuditorAware`, `DateTimeProvider`를 각각 다른 책임으로 나눠 봐야 한다
구성 요소 역할 실무 포인트
`@EntityListeners` 엔티티에 리스너 연결 JPA 표준, 엔티티/매핑 슈퍼클래스에 적용 가능
`AuditingEntityListener` persist/update 시 감사 정보 채우기 Spring Data JPA가 제공하는 구현체
`@CreatedDate`, `@LastModifiedDate` 생성일, 수정일 추적 Instant, Date, Calendar, long/Long 등에 적용 가능
`@CreatedBy`, `@LastModifiedBy` 생성자, 수정자 추적 `AuditorAware` 구현이 필요
`@EnableJpaAuditing` 감사 인프라 활성화 설정 클래스에서 켜 줘야 함
`AuditorAware` 현재 사용자/주체 제공 Spring Security와 자주 연결됨
`DateTimeProvider` 현재 시각 제공 테스트나 타임존 제어가 필요할 때 커스터마이징 가능

기본 구현

가장 전형적인 사용법은 엔티티에 `@EntityListeners(AuditingEntityListener.class)`를 붙이고 감사 어노테이션을 선언한 뒤, 설정 클래스에서 `@EnableJpaAuditing`을 켜는 것이다
@Entity
@EntityListeners(AuditingEntityListener.class)
public class Post {

    @Id
    @GeneratedValue
    private Long id;

    @CreatedDate
    private Instant createdAt;

    @LastModifiedDate
    private Instant updatedAt;

    @CreatedBy
    private String createdBy;

    @LastModifiedBy
    private String updatedBy;
}
@Configuration
@EnableJpaAuditing
public class JpaConfig {

    @Bean
    public AuditorAware<String> auditorAware() {
        return () -> Optional.of("system");
    }
}

이 구조가 가장 기본입니다. 엔티티는 “어떤 필드를 감사 대상으로 볼지”를 선언하고, 설정은 “감사 인프라를 실제로 켠다”는 책임을 집니다.

패턴 1. persist / update 시점 동작

`AuditingEntityListener`는 엔티티가 저장되거나 수정될 때 감사 메타데이터를 채우는 리스너이며, 생성 시점과 수정 시점에 다루는 필드가 다르다

`AuditingEntityListener` Javadoc은 이 클래스가 persisting and updating entities의 auditing 정보를 포착한다고 설명합니다. 메서드 설명도 `touchForCreate`는 persist events에서 생성일과 수정일, auditor를 설정하고, `touchForUpdate`는 update events에서 수정 관련 정보를 설정한다고 설명합니다.

즉, 실무 감각으로 보면 생성 시점에는 created 계열과 modified 계열이 함께 채워질 수 있고, 수정 시점에는 last modified 계열이 갱신됩니다. 이 동작 덕분에 애플리케이션 서비스가 `entity.setUpdatedAt(now())` 같은 코드를 매번 쓸 필요가 줄어듭니다.

저장 시점 흐름
1) 애플리케이션이 엔티티를 persist/save 한다
2) JPA 생명주기 안에서 auditing listener가 호출된다
3) created/modified 관련 필드를 채운다
4) DB에 반영된다

수정 시점 흐름
1) 엔티티를 update/save 한다
2) auditing listener가 수정 시점 메타데이터를 갱신한다
3) DB에 반영된다

패턴 2. AuditorAware 와 사용자 추적

생성자와 수정자를 자동으로 채우고 싶다면 리스너만 붙이는 것으로는 부족하고, 현재 사용자나 시스템 계정을 알려 주는 `AuditorAware` 구현이 필요하다

공식 문서는 `@CreatedBy`와 `@LastModifiedBy`를 사용하는 경우 auditing infrastructure가 현재 principal을 알아야 한다고 설명하고, 이를 위해 `AuditorAware<T>` SPI를 구현해야 한다고 설명합니다.

반대로 생성일과 수정일만 추적하는 경우에는 `AuditorAware`가 필수는 아닙니다. Spring Data JPA 문서도 “creation and modification dates만 추적하는 애플리케이션은 AuditorAware가 필수는 아니다”라고 설명합니다. 그래서 단순히 `createdAt`, `updatedAt`만 자동화할 때는 더 가볍게 시작할 수 있습니다.

@Bean
public AuditorAware<String> auditorAware() {
    return () -> Optional.ofNullable(SecurityContextHolder.getContext())
            .map(SecurityContext::getAuthentication)
            .filter(Authentication::isAuthenticated)
            .map(Authentication::getName);
}
실전 팁
사용자 추적이 필요 없다면 먼저 `@CreatedDate`, `@LastModifiedDate`만 적용해도 충분합니다. 현재 사용자까지 자동 추적하려는 순간 보안 컨텍스트, 배치 계정, 비동기 실행 문맥까지 함께 고려해야 합니다.

패턴 3. BaseEntity 와 임베디드 메타데이터

실무에서는 auditing 필드를 모든 엔티티마다 직접 쓰기보다 `@MappedSuperclass` 기반 BaseEntity나 임베디드 감사 객체로 묶는 방식이 가장 흔하다

`@EntityListeners`는 엔티티뿐 아니라 `@MappedSuperclass`에도 붙일 수 있습니다. 그래서 실무에서는 공통 필드를 가진 BaseEntity를 만들고, 거기에 auditing 설정을 몰아넣는 방식이 자주 쓰입니다.

또한 공식 문서는 auditing metadata가 반드시 루트 엔티티 최상위 필드에 있을 필요는 없고, embedded one에도 둘 수 있다고 설명합니다. 즉, `AuditMetadata` 같은 값 객체를 두고 그 안에 `@CreatedDate`, `@CreatedBy`를 모으는 설계도 가능합니다.

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

    @CreatedDate
    protected Instant createdAt;

    @LastModifiedDate
    protected Instant updatedAt;
}
@Embeddable
public class AuditMetadata {

    @CreatedBy
    private String createdBy;

    @CreatedDate
    private Instant createdAt;
}

이 패턴의 장점은 감사 메타데이터 구조를 더 깔끔하게 재사용할 수 있다는 점입니다. 특히 large domain model에서는 auditing 필드를 흩뿌리기보다 묶어서 보는 편이 훨씬 낫습니다.

`@EnableJpaAuditing`와 세부 옵션

이 리스너가 실제로 동작하려면 auditing 인프라를 켜야 하며, `@EnableJpaAuditing`는 현재 사용자 조회, 날짜 제공자, 생성 시 수정일 처리 같은 세부 옵션도 함께 설정할 수 있다

`@EnableJpaAuditing`은 말 그대로 JPA auditing을 어노테이션 기반 설정으로 활성화하는 스위치입니다. 이 어노테이션을 빼먹으면 엔티티에 리스너와 감사 필드를 선언해도 기대대로 동작하지 않는 경우가 많습니다.

공식 API 문서는 이 어노테이션의 optional elements로 `auditorAwareRef`, `dateTimeProviderRef`, `setDates`, `modifyOnCreate`를 제공합니다. 즉, 현재 사용자 빈을 명시적으로 지정할 수도 있고, 날짜 생성 전략을 바꿀 수도 있으며, 생성 시 수정일도 함께 찍을지 같은 정책을 조정할 수 있습니다.

옵션 의미 기본값/의도
`auditorAwareRef` 현재 사용자 조회용 `AuditorAware` 빈 지정 명시적으로 어떤 빈을 쓸지 지정
`dateTimeProviderRef` 시간 제공자 빈 지정 테스트나 시간 정책 통제에 유용
`setDates` 생성/수정 날짜 자동 세팅 여부 기본 true
`modifyOnCreate` 생성 시 수정 필드도 함께 표시할지 기본 true

한계와 주의점

`AuditingEntityListener`는 반복 코드를 줄여 주지만, 이 리스너 하나만으로 감사 요구사항 전체가 해결되는 것은 아니며 보안 주체, 배치 계정, 비동기 처리, 시간 정책은 여전히 별도 설계가 필요하다

이 기능은 “누가 만들고 언제 수정했는가” 정도의 기술적 메타데이터에는 매우 잘 맞습니다. 하지만 실제 감사(audit) 요구사항이 더 무거운 경우, 예를 들어 변경 전/후 값 전체를 기록하거나 승인 이력까지 남기고 싶다면 별도 감사 로그 구조가 필요할 수 있습니다.

또한 `createdBy`와 `updatedBy`는 현재 보안 컨텍스트나 시스템 계정에 의존합니다. 배치 작업, 스케줄러, 비동기 이벤트 처리처럼 사용자 문맥이 약한 환경에서는 기본 구현만으로는 기대와 다를 수 있습니다.

특히 주의해야 할 지점
  • 감사 메타데이터 자동화와 완전한 감사 로그 시스템을 같은 것으로 보지 않기
  • 비동기/배치 실행에서 현재 사용자 문맥이 어떻게 채워지는지 확인하기
  • 시간대, 테스트 시간 고정, 운영 환경 시계 기준을 함께 고려하기
  • BaseEntity를 무작정 모든 도메인에 강제하지 않기

자주 하는 실수

이 기능을 쓸 때 가장 흔한 실수는 엔티티에 리스너만 붙이고 `@EnableJpaAuditing`을 빼먹거나, `@CreatedBy`를 붙였는데 `AuditorAware`를 구현하지 않는 것이다
  • 리스너는 붙였지만 auditing 인프라는 활성화하지 않음
  • @CreatedBy, @LastModifiedBy를 쓰면서 AuditorAware를 제공하지 않음
  • 감사 필드를 BaseEntity에 넣었지만 실제 엔티티가 그 구조를 상속/포함하지 않음
  • 생성 시 수정일 필드가 함께 채워지는 기본 동작을 모르고 버그로 오해함
  • 시간 필드 타입과 DB 컬럼 타입 매핑을 충분히 확인하지 않음
  • 이 기능만으로 변경 이력 전체 추적이 된다고 착각함

실무 루틴

`@EntityListeners(AuditingEntityListener.class)`를 도입할 때는 필드 선언부터 시작하지 말고, 날짜만 자동화할지 사용자까지 추적할지, 공통 상속 구조로 갈지 임베디드 메타데이터로 갈지 먼저 정하는 순서가 맞다
  1. 먼저 날짜만 추적할지, 사용자까지 추적할지 범위를 정한다.
  2. 설정 클래스에 @EnableJpaAuditing를 켠다.
  3. 사용자 추적이 필요하면 AuditorAware를 구현한다.
  4. 엔티티별 선언인지 BaseEntity인지 임베디드 메타데이터인지 구조를 결정한다.
  5. 시간 타입과 DB 컬럼 매핑 정책을 점검한다.
  6. 테스트에서 실제로 created/modified 필드가 채워지는지 검증한다.

디버깅

auditing이 안 들어갈 때는 “리스너가 안 돈다”라고 뭉뚱그리지 말고, 인프라 활성화, 엔티티 선언, AuditorAware, 시간 제공자, 실제 persist/update 여부를 순서대로 나눠 보는 편이 빠르다
1
@EnableJpaAuditing가 실제 설정 클래스에 켜져 있는지 먼저 확인한다.
2
엔티티 또는 BaseEntity에 @EntityListeners(AuditingEntityListener.class)가 선언되어 있는지 본다.
3
@CreatedBy / @LastModifiedBy를 쓴다면 AuditorAware가 실제 값을 주는지 확인한다.
4
수정일이 안 바뀐다면 실제로 dirty change와 update가 발생하는지 점검한다.
5
시간이 이상하면 DateTimeProvider와 시스템 시계, 타임존을 함께 본다.
체크리스트
- auditing 인프라 활성화 여부
- 엔티티 리스너 선언 여부
- 감사 필드 어노테이션 선언 여부
- AuditorAware 값 공급 여부
- 실제 persist/update 이벤트 발생 여부
- 시간 공급자와 타입 매핑 상태

요약

`@EntityListeners(AuditingEntityListener.class)`의 본질은 JPA 엔티티 생명주기 안에 Spring Data JPA의 감사 메타데이터 자동 채우기 로직을 연결하는 데 있으며, 이를 제대로 쓰려면 감사 필드 선언, `@EnableJpaAuditing`, 필요 시 `AuditorAware`, 공통 엔티티 구조 설계를 함께 이해해야 한다
  • ✅ `@EntityListeners`는 JPA 표준 리스너 연결 어노테이션이다.
  • ✅ `AuditingEntityListener`는 Spring Data JPA가 제공하는 감사용 JPA listener다.
  • ✅ 생성일/수정일은 `@CreatedDate`, `@LastModifiedDate`로 선언한다.
  • ✅ 생성자/수정자는 `@CreatedBy`, `@LastModifiedBy`와 `AuditorAware`로 연결한다.
  • ✅ `@EnableJpaAuditing`을 켜지 않으면 기대대로 동작하지 않을 수 있다.
  • ✅ 감사 메타데이터는 루트 엔티티 필드나 임베디드 객체에 둘 수 있다.
  • ✅ BaseEntity 패턴과 함께 쓰면 반복 선언을 줄이기 좋다.
  • ✅ 이 기능은 편리하지만, 완전한 변경 이력 감사 시스템을 대체하는 것은 아니다.
728x90