Spring Data JPA Auditing with Kotlin

tkppp·2022년 1월 31일
0

SpringBoot with Kotlin

목록 보기
7/12

JPA Auditing

Audit 은 감시하다라는 뜻으로 JPA Auditing은 리액트나 뷰의 라이프사이클 훅처럼 엔티티의 라이프사이클 훅이라고 생각할 수 있다. JPA Auditing으로 관여할 수 있는 라이프사이클은 아래와 같다.

  • @PrePersist : 엔티티가 저장(insert)되기 전 호출
  • @PostPersist : 엔티티가 저장된 후 호출
  • @PreRemove : 엔티티가 삭제되기 전 후 호출
  • @PostRemove : 엔티티가 삭제된 후 호출
  • @PreUpdate : 엔티티가 업데이트되기 전 호출
  • @PostUpdate : 엔티티가 업데이트된 후 호출
  • @PostLoad : 엔티티가 조회(select)된 후 호출

JPA Auditing 기능을 사용함으로서 업데이트 시간 갱신과 같인 특정 라이프사이클의 작업 수행시 항상 필요한 로직과 엔티티 내의 코드 중복을 피할 수 있다.

JPA Auditing 설정

  1. @EnableJpaAuditing 어노테이션을 명시해 Auditing 설정 로드
  2. 감시할 엔티티를 @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 사용하기

사용법은 간단하다. 엔티티의 메소드에 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 Auditing

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 에서 프로퍼티에 접근하고 변경되기 때문이라고 생각된다.

0개의 댓글