본문 바로가기

AuditingEntity 작성

@정소민fan2025. 8. 31. 22:59

Github 링크

 

가장 먼저 모든 엔티티들의 베이스가 될 AuditingEntity를 만들어보겠다.

이 엔티티는 작성된 시간과 마지막으로 업데이트된 시간을 자동으로 DB에 넣어줄 것이다.

더해서, 모든 엔티티들은 ID를 가지고 있어야 한다. 이 프로젝트에서 모든 아이디는 자동으로 Generate 될 것이다.

 

Spring

먼저 configuration 클래스를 만들어서 JpaAuditing을 활성화 해주자

package simpleblog.config

import org.springframework.context.annotation.Configuration
import org.springframework.data.jpa.repository.config.EnableJpaAuditing

@Configuration
@EnableJpaAuditing
class JpaConfig {


}
  • @Configuration
    • 예전에는 XML을 사용해서 스프링 컨테이너가 관리할 Bean을 정의했는데, XML은 가독성이 너무 떨어지고, 관리도 어려워서 완전한 자바 코드로 대체하기 위한 어노테이션으로 만들어졌다. 이 어노테이션이 붙은 클래스 내에 Bean을 만들어서 스프링 컨테이너로 하여금 관리하도록 만들 수 있다. 이 Bean들은 CGLIB 프록시로 인해 싱글톤이 보장된다.
  • @EnableJpaAuditing
    • Spring Data JPA의 Auditing 기능을 사용하기 위해 추가

ID를 달아주는 베이스 엔티티와 생성 시간, 업데이트 시간을 담아주는 엔티티를 만들자

package simpleblog.domain

import jakarta.persistence.Column
import jakarta.persistence.EntityListeners
import jakarta.persistence.GeneratedValue
import jakarta.persistence.GenerationType
import jakarta.persistence.Id
import jakarta.persistence.MappedSuperclass
import org.springframework.data.annotation.CreatedDate
import org.springframework.data.annotation.LastModifiedDate
import org.springframework.data.jpa.domain.support.AuditingEntityListener
import java.io.Serializable
import java.time.LocalDateTime

@EntityListeners(value = [AuditingEntityListener::class])
@MappedSuperclass
abstract class AuditingEntity : AuditingEntityId(

){
    @CreatedDate
    @Column(name = "created_at", nullable = false, updatable = false)
    var createdAt: LocalDateTime? = null
        protected set

    @LastModifiedDate
    @Column(name = "updated_at", nullable = false)
    var updatedAt: LocalDateTime? = null
        protected set

}

@EntityListeners(value = [AuditingEntityListener::class])
@MappedSuperclass
abstract class AuditingEntityId : Serializable{
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    var id: Long? = null
        protected set
}
  • @MappedSuperclass
    • 이 클래스는 테이블과 직접 매핑되는 엔티티가 아니라, 이 클래스를 상속하는 엔티티에게 필드만 물려주는 역할임을 나타내는 어노테이션
  • @EntityListener(value = [AuditingEntityListener::class])
    • 이 엔티티의 라이프 사이클 이벤트를 감지할 리스너를 지정하는 어노테이션.
    • AuditingEntityListener 는 Spring Data JPA에서 제공하는 기능으로, 엔티티가 생성되거나 수정될 때 특정 동작을 수행함
  • @CreateDate
    • 이 어노테이션이 달려있는 필드는 이 엔티티가 처음 persist 될 때, 현재 시간을 자동으로 주입한다.
    • LocalDateTime만 되는건지, LocalDate 같은 다른 필드도 되는건지는 좀 더 찾아봐야겠다.
  • @LastModifiedDate
    • 이 어노테이션이 달려있는 필드에 이 엔티티가 수정될 때마다 현재 시간을 자동으로 주입한다.
  • @Column
    • 데이터베이스 컬럼에 대한 설정.
    • name : 테이블의 컬럼 이름
    • nullable : 컬럼의 null 허용 여부
    • updatable : 값의 변경 허용 여부
  • @GeneratedValue
    • Primary Key의 생성 전략을 설정하는 어노테이션. 4가지 전략이었나 있었던 것 같다
    • IDENTITY : auto-increment 기능을 사용하여 주 키 설정, DB에 저장될 때 id가 생성되기 때문에 즉시 insert 쿼리를 실행해야 해서, 대량의 데이터를 작성할 때 그만큼의 쿼리를 DBMS에 보내야하기 때문에 IO 비용이 많이 듬
    • SEQUENCE : DB의 시퀀스 오브젝트를 조회해서 미리 ID값을 할당받고, 그 값을 엔티티에 설정하여 영속성 컨텍스트에 저장하는 전략. 미리 ID값을 가져올 수 있기 때문에 배치 처리가 가능함.
    • TABLE : 키 생성 전용 테이블을 하나 만들고 이 테이블의 값을 조회하면서 기본 키를 생성하는 전략. 시퀀스 전략을 흉내내었다는데, 엔티티를 생성할때마다 키 생성 테이블을 조회하고 업데이트해야해서 성능이 매우 나쁘다고 한다. 그럼 이 전략은 대체 왜 만든거지?
    • AUTO : 각 DBMS에 가장 알맞은 방식 중 하나를 골라서 자동으로 사용. 개발자가 원하던 전략을 사용하지 못할수도 있어서 예상치 못한 문제를 만날수도 있다. default 값임.

어노테이션은 이정도면 될 것 같고...

코틀린 문법을 살펴보자

코틀린

모든 문법을 포스팅하지는 않고, 자바와 다른 점만 작성해보겠다

var과 val

자바 17버전인가? 에서도 var 문법이 있는걸로 안다. 자바에서는 var로 자동으로 타입을 추론해서 쓰이게 해주는데, 뭐 잘 쓰이지는 않는 것 같더라

코틀린에서는 var은 variable, 수정 가능한 타입이다. val은 value, 자바의 final과 같은 문법이다.

코틀린의 null 처리

자바에서는 NPE를 처리하기가 참 난감하다. Optional<T>를 쓸수도 있지만... 몇번 써봤는데 굉장히 복잡하긴 하다.

코틀린에서는 모든 타입에 기본적으로 null을 허용하지 않는다. null이 들어갈수도 있다면 ? 를 사용해야 한다.

var createdAt: LocalDateTime? = null

그럼 null 처리를 어떻게 해줄까?

Safe Call Operator

val text: String? = "Hello"
val textNull: String? = null

// text가 null이 아니므로 .length를 실행하여 5를 반환
val length1: Int? = text?.length
println(length1) // 5

// textNull이 null이므로 .length를 실행하지 않고 null을 반환
val length2: Int? = textNull?.length
println(length2) // null

// 자바 코드와 비교
// if (text != null) {
//     length = text.length();
// } else {
//     length = null;
// }

 

밑의 null을 체크하는 자바 코드와 비교하면 정말 간결하다. 삼항 연산자로 복잡하게 쓰던 적도 있었는데, 이렇게 쓰면 정말 편하다.

눈물이 날 지경

위 코드는 ? 가 붙은 변수가 null일 경우 null을 그대로 반환하고, 아니라면 해당 변수의 필드를 반환한다.

그러면 null 대신 직접 내가 원하는 변수를 반환하려면 어떻게 하면 될까?

엘비스 연산자

val text: String? = null

// text?.length가 null이므로, 엘비스 연산자 오른쪽의 0이 length 변수에 할당됨
val length: Int = text?.length ?: 0
// 자바의 경우
// int length = (text != null) ? text.length() : 0;
println(length) // 0

// 엘비스 연산자는 기본값 외에도 함수 호출, 예외 발생 등 다양한 로직을 넣을 수 있습니다.
fun getUsername(user: User?): String {
    return user?.name ?: "Guest" // 이름이 없으면 "Guest" 반환
}

fun getUserIdOrThrow(user: User?): Long {
    return user?.id ?: throw IllegalArgumentException("User ID가 없습니다!") // ID가 없으면 예외 발생
}

바로 이렇게 엘비스 연산자를 사용하면 된다. 변수가 null일 때 자신이 원하는 값을 반환할수도 있고, 예외를 터트릴 수도 있다.

let  함수

또는 nullable 변수가 null이 아닐 때 특정 코드 블록을 실행할수도 있다.

val name: String? = "Kotlin"

// name이 null이 아니므로 let 블록이 실행됨
name?.let {
    // 이 블록 안에서 it은 name이 됨. 람다식 안써도 됨
    println("이름의 길이: ${it.length}")
    println("대문자 이름: ${it.uppercase()}")
}

// it 대신 람다식을 써도 무방
name?.let { nonNullName ->
    println("이름: $nonNullName")
}

뭐 if (name != null) 처럼 쓸수도 있는데, 이게 더 깔끔한가? 하여튼 이렇게 사용할수도 있다.

 

이렇게 Auditing 엔티티를 작성해두었다. 앞으로 작성될 엔티티들은 모두 이 엔티티들을 상속받기만 하면 끝!!

'Spring > Kotlin' 카테고리의 다른 글

마이그레이션 계획  (0) 2025.12.03
서비스, API 작성  (2) 2025.10.01
리포지토리 만들기  (0) 2025.09.13
도메인 엔티티 작성  (0) 2025.09.01
코틀린 + 스프링 프로젝트  (1) 2025.08.31
정소민fan
@정소민fan :: 코딩은 관성이야

코딩은 관성적으로 해야합니다 즐거운 코딩 되세요

목차