본문 바로가기
Framework and Tool/JPA

JPA - 값 타입 - 임베디드 타입

by ocwokocw 2021. 7. 20.

- 참조: 자바 ORM 표준 JPA 프로그래밍

- 값 타입

JPA 의 데이터 타입을 분류하면 엔티티 타입과 값 타입이 있다. 엔티티 타입은 @Entity 어노테이션으로 정의했던 객체들이고, 값 타입은 int, String 등 기본 타입이나 값으로 사용하는 객체를 말한다.

 

엔티티는 식별자가 있다. 우리는 어떤 사람이 자라면서 키나 몸무게가 달라진다고해서 그 사람을 다른사람이라고 인식하지 않는다. 반면 값은 100 에서 200 으로 변하면 완전히 다른 값이 된다. 이는 DDD에서 Value Object 와 Entity 를 구분하는 내용에도 등장하는 개념이므로 헷갈린다면 이번 기회에 이해해두도록 하자.


- 기본값 타입

@Entity
public class Member extends DateMarkable{

	@Id @GeneratedValue
	@Column(name = "MEMBER_ID")
	private Long id;
	
	private String name;
	private String city;
	private String street;
	private String zipcode;

 

기본값 타입은 너무 익숙하므로 간단하게 살펴보고 넘어가도록 하자. 위의 코드에서 name~zipcode 속성은 값 타입이다. id 식별자 값에 의존하여 생명주기를 가진다.


- 임베디드 타입

JPA 에서는 값 타입을 정의해서 사용할 수 있는데 이를 임베디드 타입이라고 한다. 

 

@Entity
public class Member {

	@Id
	@Column(name = "USER_ID")
	private String id;
	
	private String userName;
	
	@Temporal(TemporalType.DATE)
	private Date startDate;
	@Temporal(TemporalType.DATE)
	private Date endDate;
	
	private String city;
	private String street;
	private String zipcode;

 

위의 코드를 가장 단순하게 설명한다면 "회원 엔티티는 이름과 근무 시작일, 근무 종료일, 도시, 번지, 우편번호를 가진다." 라고 표현할 수 있다. 

 

하지만 우리는 이정도로 단순하지는 않다. 좀더 축약해서 표현하면 "회원 엔티티는 이름과 근무기간, 주소정보를 가지고 있다." 라고도 말할 수 있다. 객체지향은 공통된 규칙과 부분을 추출해서 추상화를 하고 필요 없는 세부사항을 제거해야한다. 단순한 속성들을 타입으로 만들어 추상화시켜보자.

 

@Entity
public class Member {

	@Id
	@Column(name = "USER_ID")
	private String id;
	
	private String userName;
	
	@Embedded
	private Period perid;
	
	@Embedded
	private Address address;

 

Member 엔티티는 위와 같이 한눈에 알아보기 더 쉽게 변경되었다.

 

@Embeddable
public class Period {

	@Temporal(TemporalType.DATE)
	private Date startDate;
	@Temporal(TemporalType.DATE)
	private Date endDate;

..........

@Embeddable
public class Address {

	@Column(name = "city")
	private String city;
	private String street;
	private String zipcode;

 

@Embeddable 어노테이션을 이용하여 기간과 주소정보를 정의하면 된다. 이렇게 코드를 작성하면 응집력이 상당히 높아진다. 또한 각 타입에서 getter/setter 뿐만 아니라 의미 있는 메소드도 제공할 수 있다.

 

임베디드 타입은 기본 생성자가 필수이다. 임베디드 타입을 포함한 모든 값 타입은 엔티티의 생명주기에 의존하므로 UML 에서 컴포지션 관계가 된다.

위의 UML 에서 Period 와 Address 는 composition 관계이다. composition 은 aggregation 과 헷갈리는 경우가 많은데 가장 주요한 차이점은 생명주기의 의존성이다. Period 와 Address 는 Member 가 소멸하면 같이 소멸되므로 composition 이며, 만약 생명주기는 같이 하지는 않지만 관련성이 있다라고 표현하려면 aggregation 기호를 사용한다.


- 임베디드 타입과 연관관계

임베디드 타입은 값 타입을 포함하거나 엔티티를 참조할 수 있다. 아래는 값 타입 Zipcode 를 포함하는 Address 임베디드 타입이다.

 

@Embeddable
public class Address {

	@Column(name = "city")
	private String city;
	private String street;
	
	@Embedded
	private Zipcode zipcode;.

.........

@Embeddable
public class Zipcode {

	private String zip;
	private String plusFour;

 

또한 임베디드 타입은 엔티티도 참조할 수 있다.

 

@Embeddable
public class PhoneNumber {

	private String areaCode;
	private String localNumber;
	
	@ManyToOne
	private PhoneServiceProvider provider;

.........

@Entity
public class PhoneServiceProvider {

	@Id 
	private String name;

 

Member 에 위의 임베디드 타입을 포함시켜서 실행해보자.

 

@Entity
public class Member {

	@Id
	@Column(name = "USER_ID")
	private String id;
	
	private String userName;
	
	@Embedded
	private Period perid;
	
	@Embedded
	private Address address;

	@Embedded
	private PhoneNumber phoneNumber;
	
	@ManyToOne(fetch = FetchType.EAGER)
	@JoinColumn(name = "TEAM_ID", referencedColumnName = "TEAM_ID")
	private Team team;
	
	@OneToMany(mappedBy = "member", fetch = FetchType.LAZY)
	private List<Order> orders;

컬럼을 살펴보면 @Embedded 로 포함시킨 값 타입들의 Field 들이 Member 테이블에 포함되어있다.

 

그런데 @Embedded 로 여러 값 타입들을 포함시키다보면 중복된 Field 들이 있을 수 있다. 이럴 때에는 @AttributeOverride 어노테이션으로 속성을 재정의 해야 한다.

 

@Entity
public class Member {

	@Id
	@Column(name = "USER_ID")
	private String id;
	
	private String userName;
	
	@Embedded
	private 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"))
	})
	private Address companyAddress;

...............

@Embeddable
public class Address {

	@Column(name = "city")
	private String city;
	private String street;
	private String zipcode;

name 으로 해당 값 타입의 field 명을 참조하며, @Column 으로 테이블의 컬럼명을 설정한다.

댓글