게시글, 댓글, 메모 등 거의 모든 엔티티에는 생성 시간과 수정 시간이 필요합니다. 그런데 매번 엔티티마다 이 필드를 선언하고, save() 할 때마다 LocalDateTime.now()를 직접 넣어주는 건 반복적이고 비효율적입니다.
Spring Data JPA는 이 문제를 해결하기 위해 JPA Auditing 기능을 제공합니다. 엔티티가 저장되거나 수정될 때 시간 값을 자동으로 채워줍니다.
@Getter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class Timestamped {
@CreatedDate
@Column(updatable = false)
@Temporal(TemporalType.TIMESTAMP)
private LocalDateTime createdAt;
@LastModifiedDate
@Column
@Temporal(TemporalType.TIMESTAMP)
private LocalDateTime modifiedAt;
}
@EnableJpaAuditing // 이걸 빼먹으면 시간이 null로 들어갑니다
@SpringBootApplication
public class MemoApplication {
public static void main(String[] args) {
SpringApplication.run(MemoApplication.class, args);
}
}
@Entity
public class Memo extends Timestamped {
// createdAt, modifiedAt 컬럼이 자동으로 포함됩니다
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String contents;
// ...
}
| 어노테이션 | 역할 |
|---|---|
@MappedSuperclass | 이 클래스의 필드를 상속받는 엔티티의 컬럼으로 인식시킴. Timestamped 자체는 테이블이 생기지 않습니다. |
@EntityListeners(AuditingEntityListener.class) | Auditing 기능(시간 자동 주입)을 해당 클래스에 연결합니다. |
@CreatedDate | 엔티티가 최초 저장될 때 시간을 자동 기록합니다. |
@LastModifiedDate | 엔티티가 수정될 때마다 시간을 자동 갱신합니다. |
@Temporal(TemporalType.TIMESTAMP) | DB에 날짜+시간 형태(2026-01-01 20:21:23.454)로 저장합니다. (DATE, TIME도 가능) |
@EnableJpaAuditing | SpringBoot에게 JPA Auditing 기능을 사용하겠다고 알려줍니다. |
Timestamped 자체를 new Timestamped()로 생성할 일은 없습니다. 오직 다른 엔티티가 상속받아 사용하는 용도이므로 abstract로 선언하는 것이 적절합니다.
@Temporal은 원래 java.util.Date나 java.util.Calendar 타입을 매핑할 때 필수였습니다. LocalDateTime을 사용하는 경우 Hibernate가 자동으로 타입을 인식하기 때문에 생략해도 동작합니다. 다만 명시적으로 적어두면 의도가 분명해지는 장점이 있어서, 붙여두는 것이 좋은 습관입니다.
Memo 저장(persist) → AuditingEntityListener 동작
→ createdAt에 현재 시간 자동 세팅
→ modifiedAt에 현재 시간 자동 세팅
Memo 수정(dirty checking) → AuditingEntityListener 동작
→ createdAt은 updatable=false이므로 변경 안 됨
→ modifiedAt만 현재 시간으로 갱신
이렇게 한 번 세팅해두면, 이후 만드는 모든 엔티티에서 extends Timestamped만 붙이면 생성/수정 시간이 자동 관리됩니다. 실무에서도 거의 모든 프로젝트에서 이 패턴을 사용합니다.