본문 바로가기

IT/JPA

JPA @Embedded 및 @Embeddable에 대해 알아보자

이번 시간에는 JPA 값타입에 하나인 임베디드 타입에 대해서 알아보겠습니다.
@Embeddable, @Embedded 어노테이션을 사용해서 구현합니다.

- @Embeddale

// writer.kt
@Embeddable
class Writer(

    @Enumerated(EnumType.STRING)
    var writerType: UserType,

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "writer_seller_id")
    var writerSeller: Seller? = null,

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "writer_staff_id")
    var writerStaff: Staff? = null,

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "writer_customer_id")
    var writerCustomer: Customer? = null,

)

- @Embedded

JPA 어노테이션 @Embedded는 유형을 다른 엔티티에 임베드하는 데 사용됩니다.

@Embedded 어노테이션을 추가하고 Writer를 사용하도록 했습니다.

그리고 추상클래스로 만들어 사용할 엔티티에 오버라이딩해서 사용할 것입니다.

// Reply.kt
@MappedSuperclass
abstract class Reply(
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    open val id: Long,

    @Embedded
    @AssociationOverrides(
        AssociationOverride(name = "writerSeller", joinColumns = [JoinColumn(name = "tag_seller_id")]),
        AssociationOverride(name = "writerStaff", joinColumns = [JoinColumn(name = "tag_staff_id")]),
        AssociationOverride(name = "writerCustomer", joinColumns = [JoinColumn(name = "tag_customer_id")])
    )
    open val tagUser: Writer,
)

- 속성 재정의

위에 보시면 @AttributeOverrides속성을 사용해서 Writer포함된 유형의 열 속성을 재정의 한 것을 볼수 있습니다.

- 임베디드 타입과 테이블 매핑

임베디드 타입은 엔티티의 값이다. 그래서 기본타입을 사용했을때와 임베디드 타입을 사용했을때 데이터베이스

테이블의 결과는 동일합니다.

임베디드 타입을 사용함으로 데이터베이스와 객체를 좀 더 세밀하게 매핑가능하며 좀 더 객체지향적인 개발적인 설계를 할 수 있습니다.

  • 임베디드 타입이 Null이면 매핑한 컬럼 값은 모두 Null이 된다.
  • 값 타입의 실제 인스턴스를 공유하지 말고 Copy해서 사용해야 한다. 그냥 수정 불가하기 하길 바랍니다.(불변객체)
  • 값타입을 비교
    • 동일성 비교 : 인스턴스의 참조 값을 비교 (==사용)
    • 동등성 비교 : 인스턴스의 값을 비교 (equals()사용)

- 값 타입 컬렉션

값 타입을 하나 이상 저장하려면 컬렉션에 보관합니다. @ElementCollection, @CollectionTable 어노테이션 사용합니다.

@Entity
class Shipping(
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    val id: Long,

      ...

      @Embedded
      val adddress: Address,

      @ElementCollection
      @CollectionTable(name="Address", joinColumns = @JoinColumn(name="customer_id"))

      val addressHistory: List<Address>
)
// Address.kt
@Embeddable
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy::class)
class Address(
    @Column
    @field:NotNull
    @field:NotBlank
    @field:Length(min = 4, max = 10)
    val zipCode: String?,

    @field:NotNull
    @field:NotBlank
    @field:Length(min = 2, max = 50)
    val addressLine: String?,
    @field:Length(max = 50)
    val addressLine2: String?
)

위의 Shipping 엔티티의 addressHistory는 Address타입을 컬렉션으로 가지고 있습니다.

관계형 데이터베이스에서는 컬럼안에 컬렉션을 포함할 수 없습니다.(Nosql은 가능)

그러므로 별로의 테이블로 존재해야 합니다.

- 값 타입 컬렉션의 제약사항

  • 값 타입은 식별자라는 개념이 없어 값을 변경하면 데이터베이스 추적이 어렵습니다.

  • 값타입은 컬렉션보다는 일대다 관계로 설계하는 것을 추천합니다.

정리

오늘은 @Embbeddable, @Embedded를 사용하여 클래스를 추상클래스안에 포함 시켜 데이터베이스에 매핑하는 것을 알아 보았습니다.

임베디드 타입 사용시 장단점에 대해서도 잘 숙지하시고 사용하시는게 좋을듯 싶습니다.