지난 프로젝트에서 어떤 객체를 생성할 때 생성일과 수정일의 기록을 남겨두는 것도 중요한 데이터가 될 수 있으니 createdAt 과 modifiedAt을 구현해볼 것을 피드백 받았었다.
그땐 단순히 생성할 때, 업데이트될 때 마다 아래와 같은 방법으로 값을 지정해주었었다.
@Column(name = "created_at")
var createdAT: LocalDateTime? = LocalDateTime.now()
...
...
modifiedAt = LocalDateTime.now()
하지만 이 방법은 필요한 부분에 매번 추가를 해주어야 하고, 생각지 못한 경로로 값이 수정되었을 땐 감지하기 어렵다는 단점이 있었다.
이에 Auditing 이란 기능을 알게되어 바로 적용해보기로 했다.
감사하다 ( 보고 , 조사하다 ) 라는 뜻으로 쓰이는 단어로, 시스템이나 프로세스에서 특정 이벤트나 활동을 기록하고, 모니터링하는 프로세스를 말한다. Spring 프레임 워크에서 Auditing 은 데이터베이스 레코드의 생성 및 수정과 같은 주요 이벤트에 대한 자동 로깅 및 추적을 의미한다.
주로 JPA와 함께 사용되며, @CreatedDate, @LastModifiedDate 등의 어노테이션을 사용하여 감사를 구현할 수 있다.
Auditing 기능을 활성화 시키기 위해선 메인 어플리케이션 클래스에 @EnableJpaAuditing 옵션을 붙여주어야 한다.
@SpringBootApplication
@EnableJpaAuditing // Auditing 기능 활성화
class JangTrelloApplication
fun main(args: Array<String>) {
runApplication<JangTrelloApplication>(*args)
}
다음으론 Auditing 을 적용할 엔터티 클래스에 @EntityListeners 어노테이션을 설정해야 한다.
@EntityListeners는 엔터티의 생명주기 이벤트를 수신하는 리스너를 등록하는데 사용된다. 즉 엔터티의 상태변화( 생성, 수정, 삭제 등) 시점에 특정 동작을 수행할 수 있게 된다.
@EntityListeners
@Entity
@Table(name = "card")
class Card(
...
@CreatedDate
@Column(name = "created_at", nullable = true, updatable = true)
var createdAt : LocalDateTime? = null
@LastModifiedDate
@Column(name = "modified_at", nullable = true, updatable = true)
var modifiedAt : LocalDateTime? = null
위처럼 어노테이션을 붙여주면 created_at 필드에 최초엔 null 값이 지정되어 있지만, Service 메서드를 통해 생성 및 수정 후 데이터베이스를 확인해보면 자동으로 시간 값이 채워져 있는 것을 확인할 수 있다.
위의 createdAt 과 modifiedAt 은 대부분의 엔터티에서 사용되는 필드이기 때문에 별개의 클래스로 분리하고, 이를 상속받아 사용하면 중복 코드를 줄일 수 있어 편리해진다!
@EntityListeners(AuditingEntityListener::class)
@MappedSuperclass
class BaseEntity {
@CreatedDate
@Column(name = "created_at", nullable = true, updatable = true)
var createdAt : LocalDateTime? = null
@LastModifiedDate
@Column(name = "modified_at", nullable = true, updatable = true)
var modifiedAt : LocalDateTime? = null
}
@MappedSuperclass 어노테이션은 JPA에서 엔터티 클래스의 공통 속성을 정의하고, 이를 여러 엔터티에서 상속받아 사용할 때 사용된다. 즉 부모 엔터티에서 설정해놓은 필드를 자녀 엔터티에서 사용할 때 각각의 테이블에 "created_at" 과 "modified_at" 컬럼을 만들어 사용하게 되는 것이다.
위 클래스를 생성한 뒤 기존 엔터티들에 상속받게 하면서 @EntityListeners 와 createdAt , modifiedAt 은 삭제해버리면 된다.
@Entity
//@EntityListeners(AuditingEntityListener::class) // 삭제
@Table(name = "users")
class User (
// BaseEntity 상속받아 사용하기로. 삭제
// @CreatedDate
// @Column(name = "created_at")
// var createdAt: LocalDateTime? = null,
//
// @LastModifiedDate
// @Column(name = "modified_at")
// var modifiedAt: LocalDateTime? = null,
...
) : BaseEntity() { // BaseEntitiy() 클래스 상속
...
}
이렇게 BaseEntity 를 통해 공통부분을 작성하는 것으로 중복코드를 줄이고, 변경에 용이하게 수정할 수 있었다. 필요하다면 @CreatedBy 나 @UpdatedBy 를 통해 생성/작성자 정보를 기록하는 것도 가능하다.