본문 바로가기

서버/SpringBoot

[kotlin] spring jpa에서 복합키를 사용하는 방법, @EmbeddedId, @IdClass

jpa에서 복합키(pk가 여러 개)를 가진 테이블을 정의하려면, 키(이하 pk)들을 따로 뺀 클래스(이하 pk 클래스)를 별도로 정의하고 entity 클래스로 가져와야 한다.

이에 대한 구현은 각각 @EmbeddedId, @IdClass를 활용한 2가지로 할 수 있다.

결론부터 말하자면 두 방법이 본질적으로는 차이가 없으나 구현하는 방법과 사용하는 방법이 다르니 편한 것으로 선택하면 된다.

@EmbeddedId

  • pk클래스에 **@**Embeddable 어노테이션을 지정해줘야 한다.
  • pk클래스를 Serializable 인터페이스로 구현해야 한다.
  • pk클래스에 대한 기본 생성자가 필요하다 - build.gradle.kts에서 kotlin-jpa 플러그인 혹은 kotlin-noarg 플러그인을 추가했다면 @EmbeddedId 어노테이션을 붙이면 자동으로 기본 생성자가 만들어진다.
  • pk클래스가 public 클래스여야 함

pk 클래스와, entity 구현

@Entity
@Table(name = "person")
class Person(
    @EmbeddedId
    val personPk: PersonPk,

    @Column(name = "created_at")
    val createdAt: LocalDateTime,
)

@Embeddable
data class PersonPk(
    @Column(name = "name")
    val name: String,

    @Column(name = "phone_number")
    val phoneNumber: String,
) : Serializable

사용 방법

interface PersonRepository : JpaRepository<Person, PersonPk>
val findAll: List<Person> = PersonRepository.findAll()

val person = Person("juhi", "82012341")
val findById: Person? = PersonRepository.findById(person).orElse(null)

// pk 중 한 값에 접근하고 싶을 때
// pk테이블을 호출하고 -> 내부 컬럼을 호출할 수 있다.
val name = findById?.personPk.name 

 

키 컬럼에 바로 접근하지 못한다.

@IdClass

  • pk클래스에 대한 별도 어노테이션은 필요 없다.
  • pk클래스를 Serializable 인터페이스로 구현해야 한다.
  • pk클래스에 대한 기본 생성자가 필요하다
  • pk클래스가 public 클래스여야 한다.
  • entity 클래스에 pk클래스의 프로퍼티들을 id로 다시 한 번 정의해야 한다.
  • entity 클래스에 @IdClass로 pk 클래스를 지정해야 한다

pk 클래스와, entity 구현

@Entity
@Table(name = "person")
@IdClass(PersonPk::class)
class Person(
		@Id
    @Column(name = "name")
    val name: String,

		@Id
    @Column(name = "phone_number")
    val phoneNumber: String,

    @Column(name = "created_at")
    val createdAt: LocalDateTime,
)

data class PersonPk(
    val name: String? = null,
    val phoneNumber: String? = null,,
) : Serializable

pk클래스에 대하여 기본 생성자가 필요하기 때문에 기본값으로 null을 지정해줬다. (별도로 붙이는 어노테이션이 없기 때문에 자동으로 기본 생성자가 만들어지지 않는다.)

사용 방법

interface PersonRepository : JpaRepository<Person, PersonPk>
val findAll: List<Person> = PersonRepository.findAll()

val person = Person("juhi", "82012341")
val findById: Person? = PersonRepository.findById(person).orElse(null)

// pk 중 한 값에 접근하고 싶을 때 해당 컬럼에 바로 접근할 수 있다. 
val name = findById?.name 

키 컬럼에 바로 접근할 수 있다. 

차이점

  • 물리적인 모델 관점에서 차이점은 없다.
  • EmbeddedId는 결합된 pk클래스 자체가 의미 있을 때, 이를 분명하게 전달할 수 있다.
  • IdClass는 데이터의 각 키에 접근하는 일이 많을 경우 유용하다.
반응형