Audit
은 감시하다라는 뜻으로 JPA Auditing은 리액트나 뷰의 라이프사이클 훅처럼 엔티티의 라이프사이클 훅이라고 생각할 수 있다. JPA Auditing으로 관여할 수 있는 라이프사이클은 아래와 같다.
@PrePersist
: 엔티티가 저장(insert)되기 전 호출@PostPersist
: 엔티티가 저장된 후 호출@PreRemove
: 엔티티가 삭제되기 전 후 호출@PostRemove
: 엔티티가 삭제된 후 호출@PreUpdate
: 엔티티가 업데이트되기 전 호출@PostUpdate
: 엔티티가 업데이트된 후 호출@PostLoad
: 엔티티가 조회(select)된 후 호출JPA Auditing 기능을 사용함으로서 업데이트 시간 갱신과 같인 특정 라이프사이클의 작업 수행시 항상 필요한 로직과 엔티티 내의 코드 중복을 피할 수 있다.
@EnableJpaAuditing
어노테이션을 명시해 Auditing 설정 로드@EntityListeners(AuditingEntityListener::class)
어노테이션으로 등록// Post.kt
@Entity
@EntityListeners(AuditingEntityListener::class)
class Post{ ... }
// SpringBootApplication.kt
@SpringBootApplication
@EnableJpaAuditing
class SpringBootApplication
fun main(args: Array<String>) {
runApplication<SpringBootApplication>(*args)
}
사용법은 간단하다. 엔티티의 메소드에 JPA Auditing 어노테이션을 붙이면 해당 라이프사이클에 메소드가 실행된다.
// Post.kt
@Entity
@EntityListeners(AuditingEntityListener::class)
class Post(
title : String,
content: String
) {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long? = null
@Column(length = 500, nullable = false)
var title: String = title
protected set
@Column(columnDefinition = "TEXT", nullable = false)
var content: String = content
protected set
fun update(updateDto: PostUpdateRequestDto){
title = updateDto.title
content = updateDto.content
}
@PrePersist
@PreUpdate // 어노테이션을 중첩하여 여러 라이프사이클에서 메소드를 실행할 수 있다.
fun testAuditing(){
println("PrePersist, PreUpdate Lifecycle")
}
}
Spring Data Jpa는 자주 사용되는 특정 작업에 대해 추상화된 기능을 간편하게 제공한다.
@CreatedDate
, @LastModifiedDate
: 매핑된 엔티티 프로퍼티에 엔티티의 저장 시간, 업데이트(갱신) 시간을 자동으로 관리해준다.@CreatedBy
, @LastModifiedBy
: 매핑된 엔티티 프로퍼티에 엔티티가 저장되거나 갱신될 때 이를 수행한 유저를 자동으로 관리해준다. (스프링 시큐리티와 같이 사용)@MappedSuperclass
@EntityListeners(AuditingEntityListener::class)
abstract class BaseTimeEntity {
@CreatedDate
@Column(nullable = false, updatable = false)
var createdAt: LocalDateTime = LocalDateTime.now()
protected set
@LastModifiedDate
@Column(nullable = false)
var modifiedAt: LocalDateTime = LocalDateTime.now()
protected set
}
@Entity
class Post(
title : String,
content: String
) : BaseTimeEntity() {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long? = null
@Column(length = 500, nullable = false)
var title: String = title
protected set
@Column(columnDefinition = "TEXT", nullable = false)
var content: String = content
protected set
val author: String? = null
fun update(updateDto: PostUpdateRequestDto){
title = updateDto.title
content = updateDto.content
}
}
굳이
@MappedSuperClass
를 통해 엔티티를 상속해 구현할 필요는 없다. 다만 생성시간, 갱신시간은 여러 엔티티에서 사용되므로 상속을 통해 코드의 중복을 막을 수 있다.
createdAt과 같이 변경 가능성이 없는 프로퍼티라 해서 val로 선언하면 컴파일 에러가 발생하므로 var로 선언해야한다. 위의 코드에서 프로퍼티의 기본값으로 프로퍼티의 현재 시간을 넣어주었지만 Data Jpa Auditing 내부적으로 숨겨진 @PrePersist, @PreUpdate 에서 프로퍼티에 접근하고 변경되기 때문이라고 생각된다.