본 시리즈는 작성자의 이해와 경험을 바탕으로 실습 위주의 설명을 제공하고자 작성되었습니다.
실습 중심의 이해를 목표로 작성되었기 때문에, 다소 과장되거나 생략된 부분이 있을 수 있습니다.
따라서, 이론적으로 미흡한 부분이 있을 수 있는 점에 유의하시고 양해 부탁드립니다.
또한, Spring Boot 기반의 Backend 개발에 중점을 두고 설명하고 있으므로,
Frontend와 관련된 내용은 별도의 참고자료를 검색/활용하실 것을 권장드립니다.
과거
Java 8
,Spring Boot 2.x
를 사용하던 시절에는,JPA
를 사용하는 경우JPA Auditing
기능을 활용해 엔티티의 생성 시간과 수정 시간을 자동으로 관리했습니다. 이 방식은 특히,BaseTimeEntity
라는 공통 상속 클래스를 만들어, 여러 엔티티에서 상속받아 사용함으로써, 중복 코드를 제거하는데 매우 유용했습니다.
그러나, 시간이 흘러
Spring Boot 2.x
의 지원이 종료되고,Java 17
,Spring Boot 3.x
의 시대로 넘어오며,Hibernate
가Spring Boot
와 좀 더 밀접히 통합되면서,Hibernate
에서 제공하는@CreationTimestamp
와@UpdateTimestamp
널리 사용되기 시작했습니다.
하지만, 오래된 시스템이나 Hibernate
가 사용되지 않는 시스템 등 JPA Auditing
기능을 활용한 BaseTimeEntity
는 지금도 여전히 많이 사용되는 패턴 중 하나입니다.
따라서, 이번 글에서는 JPA Auditing
과 BaseTimeEntity
의 사용 방식과
Hibernate
어노테이션 기반 방식의 차이와 각각의 장단점을 알아보겠습니다.
├── src
│ ├── main
│ │ ├── java
│ │ │ └── com.companyname.projectname
│ │ │ ├── common
│ │ │ │ └── model
│ │ │ │ └── BaseTimeEntity.java # 공통 상위 엔티티
│ │ │ ├── post
│ │ │ │ └── model
│ │ │ │ └── Post.java
│ │ │ └── ProjectNameApplication.java
│ │ └── resources
│ └── test
package com.companyname.projectname.common.model;
import jakarta.persistence.Column;
import jakarta.persistence.EntityListeners;
import jakarta.persistence.MappedSuperclass;
import lombok.Getter;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import java.time.LocalDateTime;
@Getter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseTimeEntity {
@CreatedDate
@Column(updatable = false)
private LocalDateTime createdDate;
@LastModifiedDate
@Column(updatable = true)
private LocalDateTime updatedDate;
}
Lombok
과JPA Auditing
을 사용한 추상화 클래스로 구현되었으며,
날짜/시간 등의 데이터는 거의 대부분의 데이터에 포함되는 공통 속성으로
여러 엔티티에서 상속받아 시간 관리 로직을 중복 코드 없이 재사용 가능합니다.
package com.companyname.projectname.post.model;
import com.companyname.projectname.common.model.BaseTimeEntity;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Entity
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Post extends BaseTimeEntity { // 상속
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
private String content;
}
@Entity
로 명시된 엔티티 클래스에서BaseTimeEntity
를 상속받습니다.
package com.companyname.projectname;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
@EnableJpaAuditing // JPA Auditing 활성화
@SpringBootApplication
public class ProjectNameApplication {
public static void main(String[] args) {
SpringApplication.run(ProjectNameApplication.class, args);
}
}
JPA Auditing
을 사용하려면 메인 클래스에서JPA Auditing
을 활성화해야 합니다.
Spring Boot
프로젝트의 메인 클래스는ProjectNameApplication.java
이며,
@SpringBootApplication
어노테이션이 기본으로 작성되어있습니다.
여기에@EnableJpaAuditing
를 추가합니다.
├── src
│ ├── main
│ │ ├── java
│ │ │ └── com.companyname.projectname
│ │ │ ├── post
│ │ │ │ └── model
│ │ │ │ └── Post.java
│ │ │ └── ProjectNameApplication.java
│ │ └── resources
│ └── test
package com.companyname.projectname.post.model;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import java.time.LocalDateTime;
@Entity
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Post {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
private String content;
@CreationTimestamp
private LocalDateTime createdDate;
@UpdateTimestamp
private LocalDateTime updatedDate;
}
Hibernate
의@CreationTimestamp
,UpdateTimestamp
어노테이션을 추가합니다.
데이터 삽입/수정 작업 실행 결과, 연결된 데이터베이스에 Post
테이블이 생성되고,
상속 받은 BaseTimeEntity
객체에 의해 '날짜/시간' 속성도 생성됩니다.
- 장점
- 시간 관리 로직을 중복 코드 없이 재사용 가능
- 유지보수가 간편하고, 코드 가독성이 좋아지는 효과
Spring Data JPA
의JPA Auditing
기능을 활용해 엔티티의 생명주기와 연동
- 단점
- 모든 엔티티가
BaseTimeEntity
를 상속받아야 하므로, 상속 구조의 강제성 발생- 상속 외에는 사용할 수 없어, 특정 엔티티에서만 시간 필드를 사용할 경우
불필요한 의존성이 생김
- 장점
- 유연성: 필요한 엔티티에서만 필요시 적용 가능
- 간결성: 추가 상속 없이 엔티티 클래스에 바로 선언 가능
- 직관적: 어노테이션 이름만으로 필드의 역할을 쉽게 이해할 수 있음
- 단점
Hibernate
를 사용하지 않는 환경에서는 사용할 수 없음Spring Data JPA
의JPA Auditing
만큼 엔티티 생명주기를 세밀하게 다루기는 어려움
이 방식은 JPA 표준이 아니지만, 간결하고 유연한 시간 관리가 가능하다는 장점이 있습니다.
Hibernate
를 사용하는 프로젝트라면 @CreationTimestamp
와 @UpdateTimestamp
를 사용하는 것이 더 간단하고 현대적인 선택입니다.
그러나, 오래된 시스템이거나 Hibernate
를 사용하지 않는 시스템이라면,
Spring Data JPA
의 JPA Auditing
기능을 활용하는 BaseTimeEntity
방식이 여전히 유효합니다.
결론적으로, 선택의 기준에 따라 다릅니다.
최근 개발 환경은 간결함과 유연함을 추구하고 요구하고 있습니다.
필자는 개인적으로 코드의 가독성과 유지보수성, 확장성 등을 고려하여
Hibernate
의@CreationTimestamp
와@UpdateTimestamp
방식을 권장합니다.JPA Auditing
과
BaseTimeEntity는 여전히 유효한 방식이지만,
Hibernate의
@CreationTimestamp와
@UpdateTimestamp`를 사용하는 코드들이 늘어나고 있습니다.
그러나, 개발 트렌드는 항상 변화합니다. 늘 프로젝트의 요구 사항과
기술 스택에 맞는 적절한 방식을 선택하는 것이 가장 중요합니다.
본 시리즈는 작성자의 이해와 경험을 바탕으로 실습 위주의 설명을 제공하고자 작성되었습니다.
실습 중심의 이해를 목표로 작성되었기 때문에, 다소 과장되거나 생략된 부분이 있을 수 있습니다.
따라서, 이론적으로 미흡한 부분이 있을 수 있는 점에 유의하시고 양해 부탁드립니다.
또한, Spring Boot 기반의 Backend 개발에 중점을 두고 설명하고 있으므로,
Frontend와 관련된 내용은 별도의 참고자료를 검색/활용하실 것을 권장드립니다.