자바 백엔드 입문 48편. 모든 Entity에 생성·수정 시각·작성자를 자동으로 박는 JPA Auditing 표준 패턴. BaseEntity 상속까지 풀어쓴 학습 노트.
이 글은 자바 백엔드 입문 시리즈 59편 중 48편이에요. 회사 시스템 모든 테이블에 박히는 created_at·updated_at 컬럼 — JPA Auditing이 자동으로 박아주는 표준 패턴.
Auditing이란
JPA Auditing = "엔티티가 생성·수정될 때 시각·작성자를 자동으로 기록하는 기능". 회사 시스템 표준 — 모든 테이블에 created_at·updated_at 컬럼이 박혀 있어요.
수동으로 하면.
order.setCreatedAt(LocalDateTime.now());
orderRepo.save(order);
// ... 수정 시
order.setUpdatedAt(LocalDateTime.now());
orderRepo.save(order);
매번 반복. 매번 깜빡할 위험. Auditing이 자동 처리해줘요.
4가지 Auditing 어노테이션
| 어노테이션 | 자동 입력 |
|---|---|
@CreatedDate |
생성 시각 |
@LastModifiedDate |
마지막 수정 시각 |
@CreatedBy |
생성한 사용자 |
@LastModifiedBy |
마지막 수정한 사용자 |
처음 두 개가 가장 자주. 사용자 추적이 필요하면 뒤 두 개도.
설정 — 3단계
1. @EnableJpaAuditing 활성화
@SpringBootApplication
@EnableJpaAuditing // ← 한 줄
public class MyApp { ... }
2. BaseEntity 만들기
모든 Entity가 공통으로 가질 부모 클래스.
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
@Getter
public abstract class BaseEntity {
@CreatedDate
@Column(updatable = false) // 생성 후 변경 X
private LocalDateTime createdAt;
@LastModifiedDate
private LocalDateTime updatedAt;
}
@MappedSuperclass = "이 클래스는 테이블이 아니라 부모". 자식 Entity가 상속하면 필드를 같이 가져감.
@EntityListeners(AuditingEntityListener.class) = "이 Entity의 변화에 Auditing 리스너 박아".
3. Entity 상속
@Entity
@Table(name = "orders")
@Getter @Setter @NoArgsConstructor
public class Order extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private int amount;
private String status;
// createdAt·updatedAt 자동 상속
}
Order 가 자동으로 createdAt·updatedAt 필드 가짐. 모든 INSERT·UPDATE에 자동 박힘.
동작 확인
Order order = new Order();
order.setAmount(10000);
orderRepo.save(order);
// → INSERT 시 createdAt 자동 박힘
order.setAmount(20000);
orderRepo.save(order);
// → UPDATE 시 updatedAt 자동 박힘 (createdAt은 변경 X)
매번 손으로 시각 박을 일 없음.
@CreatedBy·@LastModifiedBy — 사용자 추적
"누가 만들고 누가 수정했는지" 도 자동 박으려면.
AuditorAware 구현
Spring에 "현재 사용자는 누군가" 알려주는 Bean.
@Component
public class SecurityAuditorAware implements AuditorAware<String> {
@Override
public Optional<String> getCurrentAuditor() {
return Optional.ofNullable(SecurityContextHolder.getContext())
.map(SecurityContext::getAuthentication)
.filter(Authentication::isAuthenticated)
.map(Authentication::getName);
}
}
(Spring Security 사용 시 — 45편에서 자세히)
@EnableJpaAuditing에 연결
@SpringBootApplication
@EnableJpaAuditing(auditorAwareRef = "securityAuditorAware")
public class MyApp { ... }
BaseEntity 확장
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
@Getter
public abstract class BaseEntity {
@CreatedDate
@Column(updatable = false)
private LocalDateTime createdAt;
@LastModifiedDate
private LocalDateTime updatedAt;
@CreatedBy
@Column(updatable = false)
private String createdBy;
@LastModifiedBy
private String updatedBy;
}
INSERT 시 createdBy = 현재 사용자 이메일, UPDATE 시 updatedBy = 수정자 자동 박힘.
자주 보강하는 컬럼들
회사 시스템에서 BaseEntity에 더 박는 필드들.
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
@Getter
public abstract class BaseEntity {
@CreatedDate
@Column(updatable = false)
private LocalDateTime createdAt;
@LastModifiedDate
private LocalDateTime updatedAt;
@Column(nullable = false)
private boolean deleted = false; // 소프트 삭제
@Version
private Long version; // 낙관적 락
}
deleted(Soft Delete) = 실제 DELETE 안 하고 플래그만. "휴지통" 패턴@Version= 동시 수정 충돌 감지 (낙관적 락)
이 4가지 박은 BaseEntity가 회사 표준 BaseEntity. 모든 Entity가 상속.
시각 타입 — LocalDateTime vs Instant vs ZonedDateTime
@CreatedDate 필드 타입 선택.
| 타입 | 의미 | 권장 |
|---|---|---|
LocalDateTime |
시간대 없음 | 단일 국가 서비스 |
Instant |
UTC 타임스탬프 | 글로벌 서비스 권장 |
ZonedDateTime |
시간대 명시 | 시간대 정보 필요 시 |
Long (epoch ms) |
숫자 | 가장 단순 |
한국 단일 서비스 = LocalDateTime 표준. 글로벌·다국적 = Instant.
모든 Entity가 같은 BaseEntity를 상속하는 게 한국 회사 표준. 시간·작성자·소프트 삭제·낙관적 락 — 4가지가 공통이라 한 곳에서 관리. 새 Entity 추가 시 extends BaseEntity 한 줄로 끝.
한 줄 정리 — @EnableJpaAuditing + BaseEntity(@MappedSuperclass + @EntityListeners) + @CreatedDate·@LastModifiedDate 세 단계로 모든 Entity 자동 Auditing. 사용자 추적은 AuditorAware 추가.
시험 직전 한 번 더 — Auditing 입문자가 매번 헷갈리는 것
- JPA Auditing = 생성·수정 시각·작성자 자동 박기
- 활성화 =
@EnableJpaAuditing메인 클래스 - 4가지 어노테이션 =
@CreatedDate·@LastModifiedDate·@CreatedBy·@LastModifiedBy - BaseEntity = 공통 필드 부모 클래스
@MappedSuperclass= 테이블 아닌 부모 (필드 상속용)@EntityListeners(AuditingEntityListener.class)= Auditing 리스너 등록- 자식 Entity는
extends BaseEntity한 줄 @Column(updatable = false)= 생성 후 변경 X (createdAt)- 사용자 추적 =
AuditorAware<String>구현 Bean @EnableJpaAuditing(auditorAwareRef = "...")로 연결- Spring Security 사용 시 =
SecurityContextHolder에서 사용자 꺼냄 - BaseEntity 자주 박는 필드 = createdAt·updatedAt·createdBy·updatedBy·deleted·version
- 소프트 삭제 =
deleted플래그로 실제 DELETE 회피 (휴지통) @Version= 낙관적 락 (동시 수정 충돌 감지)- 시각 타입 = LocalDateTime (한국), Instant (글로벌)
- 한국 회사 = 모든 Entity가 BaseEntity 상속이 표준
- 새 Entity 추가 시 =
extends BaseEntity한 줄로 자동 적용 - 마이그레이션 시 =
created_at·updated_at컬럼 추가 자동 - Auditing은 JPA save 시점에 동작 (영속화)
- 직접 SQL UPDATE는 Auditing 안 먹힘 (
@Modifying쿼리도)
시리즈 다른 편 (앞뒤 글 모음)
이전 글:
- 43편 — @Transactional의 원리
- 44편 — JPA Hibernate Spring Data JPA 셋의 관계
- 45편 — @Entity Repository JPA 두 축
- 46편 — JPA 연관관계 @OneToMany @ManyToOne
- 47편 — JPA @Embedded @Embeddable 값 객체
다음 글: