티스토리 뷰

Java

[Kotlin] JPA Entity @Id 필드 선언 방식

snail voyager 2026. 1. 11. 15:46
728x90
반응형

Body에 선언 - 안전한 Kotlin Entity 패턴 

  • id는 클래스 바디에서 var로 선언
  • id가 null이면 "아직 DB에 저장되지 않은 비영속(Transient) 상태
  • 자동 생성이 아니면 서비스/팩토리에서 member.id = "값" 형태로 세팅
  • JPA는 프록시 객체 생성을 위해 기본 생성자가 필요
    • 자동으로 기본 생성자를 만들어주는 라이브러리 "plugin.noarg", "org.jetbrains.kotlin.plugin.jpa"
  • Body에 선언하면 직접 equals와 hashCode를 구현하기 용이
  • JPA Entity의 동등성은 오직 PK(Primary Key)로만 판단해야 안전
    • 영속화 전(Transient) 객체끼리 비교할 때도 동등으로 보고 싶은 경우는 PK 비교 안됨
@Entity
class Member(
    var name: String
) {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    var id: String? = null // 자동 생성이 아니면 직접 할당

    protected constructor() : this("") // JPA 기본 생성자
}

ID를 수동 할당해야 할 경우

  • 수동으로 ID를 넣고 싶다면 별도 팩토리 메서드를 두는 것이 더 안전
@Entity
class Member(
    var name: String
) {
    @Id
    var id: String? = null

    protected constructor() : this("")

    companion object {
        fun createWithId(id: String, name: String) = Member(name).apply {
            this.id = id
        }
    }
}

Body 에 @Id 필드를 val 로 선언

  • 일반적인 코드: user.id = 1L (컴파일 에러! val이라 수정 불가)
  • Hibernate: field.setAccessible(true)를 호출하여 접근 제어자와 final 여부를 무시하고 값을 씁니다.
@Entity
class Member(
    var name: String
) {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    val id: String? = null

    protected constructor() : this("")
}

굳이 var를 써야 하는 경우?

"나는 리플렉션 같은 매직이 싫고, 정말 논리적으로 값이 변하는 거니까 var여야 한다"고 생각하신다면,

var를 쓰되 외부 수정을 막아야 합니다.

하지만 val을 쓰면 protected set 같은 코드를 안 적어도 되니 더 간결

@Entity
class Member(
    var name: String
) {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    var id: String? = null
    	protected set // 외부에서는 수정 불가, 상속받은 클래스나 JPA는 접근 가능

    protected constructor() : this("")
}

생성자에 선언하는 경우 - 권장 ❌

불변 읽기 전용 엔티티

  • JPA가 값 세팅을 할 필요가 없고,
    데이터베이스에서 Native Query + DTO 매핑 또는 @QueryProjection 같은 방식으로 즉시 생성되는 경우
  • 이때는 JPA 영속성 컨텍스트 관리 대상이 아니거나, 조회 전용 Projection 엔티티일 때만 가능
@Entity
class Member(
    @Id
    val id: String,   // 이미 DB에서 읽어온 값, 변경 없음
    val name: String
)

@Id가 수동 생성되고, 생성 시점에 값이 확정

  • 서비스 로직에서 ID를 항상 생성자 호출 전에 만들어 주는 경우
    (예: UUID, KSUID, 특정 규칙 기반 키)
  • 이 경우 엔티티 생성 시점에 ID가 이미 세팅되므로 JPA가 ID를 채워 넣을 필요가 없음
@Entity
class Member(
    @Id
    val id: String, // 서비스에서 미리 생성
    var name: String
) {
    protected constructor() : this("", "") // JPA 기본 생성자
}

생성자에 val로 선언하는 경우

@Entity
class Member(
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    val id: Long = 0,
    var name: String
)
  • val 불변 필드라도 생성된 id 값이 정상 생성
  • Hibernate는 리플렉션으로 val 필드에 직접 값을 써버림
    1. 객체를 먼저 만듦
    2. INSERT 실행
    3. DB에서 생성된 ID를 받음
    4. 리플렉션으로 final 필드에 직접 값 써버림
  • 하지만 객체 생성 시 id 값을 모른 상태로 Member(0, "Kim") 으로 생성해야하니 어색함

 

728x90
반응형
반응형
300x250