JPA - 복합 값 타입(embedded type)

이유석·2024년 1월 14일
1

JPA - Entity

목록 보기
14/14
post-thumbnail

JPA 에서의 데이터 타입

JPA는 데이터 타입을 크게 두 가지로 분류합니다.

  1. Entity 타입
    • @Entity 로 정의하는 객체
    • 식별자(ID)를 통해 지속해서 추적 가능
  1. Value 타입
    • Java 기본 타입(int, double), 래퍼 클래스(Integer, String) 처럼 단순히 값으로 사용
    • 또는, 복합 값 타입(embedded type) 과 컬렉션 값 타입과 같은 객체
    • 식별자가 없고 속성만 존재하기 때문에 추척할 수 없음

복합 값 타입(임베디드 타입, embedded type)

JPA 에서 새로운 값 타입을 직접 정의해서 사용할 수 있습니다.

  • 이것을 복합 값 타입(embedded type) 이라고 합니다.
  • Value 타입 입니다.

객체 및 테이블 모델링 (embedded type 사용 X)

회원(Member) 도메인에 근무기간(Work Period) 과 집 주소(Home Address)의 속성을 추가해 보도록 하겠습니다.

예제 코드 (embedded type 사용 X)

Member Entity 코드

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Member {

    @Id
    @Column(name = "MEMBER_ID")
    private Long id;

    @Column(name = "USERNAME")
    private String username;
    
    // 근무 기간
    @Temporal(TemporalType.DATE)
    Date startDate;
    @Temporal(TemporalType.DATE)
    Date endDate;
    
    // 집 주소 표현
    private String city;
    private String street;
    private String zipcode;
    
}
  • 위 코드는 근무 기간, 집 주소의 모든 속성을 회원 엔티티에 추가한 코드 입니다.

즉, 회원 엔티티는 이름, 근무 시작일, 근무 종료일, 집 주소 도시, 집 주소 번지, 집 주소 우편 번호 의 속성을 갖습니다.

  • 이런 엔티티는 단순히 정보를 풀어둔 것 뿐입니다. 또한 근무 시작일과 주소의 도시는 서로 아무 관련성이 없습니다.
  • 또한 회원이 상세한 데이터를 그대로 가지고 있는 것은 객체지향적이지 않으며 응집력만 떨어뜨립니다.

예제 코드 (embedded type 사용 O)

아래와 같이 표현하면 더 명확하게 표현할 수 있습니다.

회원 엔티티는 이름, 근무 기간, 집 주소를 갖습니다.

  • 이와 같이 근무 기간, 주소 같은 타입이 있다면 코드가 더 명확해 질 수 있습니다.

Member Entity 코드

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Member {

    @Id
    @Column(name = "MEMBER_ID")
    private Long id;

    @Column(name = "USERNAME")
    private String username;
    
    @Embedded Period workPeriod;    // 근무 기간
    @Embedded Address homeAddress;  // 집 주소
}

기간 임베디드 타입

@Embeddable
public class Period {
	
    @Temporal(TemporalType.DATE)
    Date startDate;
    @Temporal(TemporalType.DATE)
    Date endDate;
    // ..
    
    public boolean isWork(Date date) {
    	// .. 값 타입을 위한 메소드를 정의할 수 있습니다.
    }
}

주소 임베디드 타입

@Embeddable
public class Address {
	
    @Column (name="city") // 매핑할 컬럼 정의 가능
    private String city;
    private String street;
    private String zipcode;
}

임베디드 타입 사용을 위해서, 다음 2가지 어노테이션이 필요합니다.

  • @Embeddable : 값 타입을 정의하는 곳에 표시
  • @Embedded : 값 타입을 사용하는 곳에 표시
  • 참고로 둘 중 하나는 생략해도 사용 가능합니다.
  • 또한, 임베디드 타입은 기본 생성자가 필수 입니다.

임베디드 타입을 포함한 모든 값 타입은 엔티티의 생명주기에 의존하므로, 엔티티와 임베디드 타입의 관계를 UML로 표현하면 컴포지션(composition) 관계가 됩니다.

하이버네이트는 임베디드 타입을 컴포넌트(components)라 합니다.

embedded type 의 장점

  • @Embeddable 로 정의한 새로운 값 타입들의 재사용
  • 높은 응집도
  • Period 객체의 isWork() 메서드처럼 해당 값 타임만 사용하는 의미있는 메서드를 만들 수 있습니다.

embedded type과 테이블 매핑

  • 임베디드 타입은 엔티티의 값일 뿐입니다.
    따라서 값이 속한 엔티티의 테이블에 매핑합니다. 예제에서 임베디드 타입을 사용하기 전과 후에 매핑하는 테이블은 같습니다.

임베디드 타입 덕분에 객체와 테이블을 아주 세밀하 매핑하는 것이 가능합니다.

잘 설계한 ORM 애플리케이션은 매핑한 테이블의 수보다 클래스의 수가 더 많습니다.

embedded type 과 연관관계

  • 임베디드 타입은 값 타입을 포함하거나 엔티티를 참조할 수 있습니다.

  • 주소(Address)에 새로운 우편번호(Zipcode) 임베디드 타입을 추가합니다.
  • 휴대폰 번호(PhoneNumber) 에 PhoneServiceProvider 와의 연관관계를 매핑합니다.
@Entity
public class Member {
  
  @Embedded
  Address address;	// 임베디드 타입 포함
	@Embedded
  PhoneNumber phoneNumber;	// 임베디드 타입 포함

  // ...
}


@Embeddable
public class Address {
  String street;
  String city;
  String state;
  @Embedded
  Zipcode zipcode; // 임베디드 타입 포함
}


@Embeddable
public class Zipcode {
  String zip;
  String plusFour;
}


@Embeddable
public class PhoneNumber {
 
  String areaCode;
  String localNumber;
  
  @ManyToOne
  PhoneServiceProvider provider;	// 엔티티 참조
}


@Entity
public class PhoneServiceProvider {
  @Id
  String name;
 	// ...
}

@AttributeOverride (속성 재정의)

임베디드 타입에 정의한 매핑정보를 재정의하려면 엔티티에 @AttributeOverride를 사용하면 됩니다.

  • 회원(Member) 에게 두개의 주소 정보(집, 회사) 가 필요합니다.
// 같은 임베디드 타입을 가지고 있는 회원
@Entity
public class Member {
  
  @Id @GeneratedValue
  private Long id;
  private String name;
  
  @Embedded
  Address homeAddress;
  
  @Embedded
  Address companyAddress;
}

위 코드의 문제점은 테이블에 매핑하는 컬럼명이 중복되는 것 입니다.

@AttributeOverrides를 사용하여 매핑정보를 재 정의

@Entity
public class Member {
  
  @Id @GeneratedValue
  private Long id;
  private String name;
  
  @Embedded
  Address homeAddress;
  
  @Embedded
  @AttributeOverrides({
    @AttributeOverride(name="city", column=@Column(name="COMPANY_CITY")),
    @AttributeOverride(name="street", column=@Column(name="COMPANY_STREET")),
    @AttributeOverride(name="zipcode", column=@Column(name="COMPANY_ZIPCODE"))
  })
  Address companyAddress;
}

위 코드에 의해 생성된 테이블은 아래와 같습니다.

CREATE TABLE MEMBER (
  COMPANY_CITY varchar(255),
  COMPANY_STREET varchar(255),
  COMPANY_ZIPCODE varchar(255),
  city varchar(255),
  street varchar(255),
  zipcode varchar(255),
  ...
)
  • `@AttributeOverrides는 엔티티에 설정해야 합니다. 임베디드 타입이 임베디드 타입을 가지고 있어도 엔티티에 설정해야 합니다.

embedded type 과 null

임베디드 타입이 null 이면, 매핑한 컬럼 값은 모두 null이 됩니다.

member.setAddress(null); // null 입력
em.persist(member);

회원 테이블의 주소와 관련된 CITY, STREET, ZIPCODE 컬럼 값은 모두 null이 됩니다.

예제코드

profile
https://github.com/yuseogi0218

0개의 댓글