@Entity
public class Member {
@id @GeneratedValue
private Long id;
private String name; //값 타입
private int age; //값 타입
...
}
//name, age 속성은 식별자 값도 없고 생명주기도 Membr 엔티티에 의존한다.
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
private String name;
@Embedded Period workPeriod; //근무 기간
@Embedded Address homeAddress; //집 주소
}
@Embeddable
public class Period {
@Temporal(TemporalType.DATE) java.util.Date startDate;
@Temporal(TemporalType.DATE) java.util.Date endDate;
public boolean isWork(Date date) {
//값 타입을 위한 메소드 정의 가능
}
}
@Embeddable
public class Address {
@Column(name="city") //매핑 컬럼 정의
private String city;
private String street;
private String zipcode;
}
임베디드 타입을 포함한 모든 값 타입은 엔티티의 생명주기에 의존하므로 엔티티와 임베디드 타입의 관계를 UML로 표현하면 컴포지션 관계가 된다.
임베디드 타입과 테이블 매핑
임베디드 타입과 연관관계
@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;
...
}
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
private String name;
@Embedded Address honeAddress;
@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;
}
//위와 같이 설정 후 auto DDL 작동 시
//COMPANY_* 의 이름을 가진 컬럼들이 생성 된다.
값 타입 공유 참조
임베디드 타입 같은 값 타입을 여러 엔티티에서 공유 시 문제가 발생한다.
member1.setHomeAddress(new Address("OldCity"));
Address address = member1.getHomeAddress();
address.setCity("NewCity"); //회원1의 address 값을 공유해서 사용
member2.setHomeAddress(address);
-----------------------------------------------
이거 회원1의 city 값도 NewCity 로 바뀌게 된다. 왜냐? 같은 인스턴스를 참조하기 때문
해결방법? 값을 복사하여 사용할 것
값 타입 복사
member1.setHomeAddress(new Address("OldCity"));
Address address = member1.getHomeAddress();
Address newAddress = address.clone(); //값 복사!
newAddress.setCity("NewCity");
member2.setHomeAddress(newAddress);
불변 객체 (한번 만들면 절대 변경할 수 없는 객체)
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
@Embedded
private Address homeAddress;
@ElementCollection //값 타입 컬렉션 이용 시 사용하는 어노테이션
@CollectionTable(name = "FAVORITE_FOODS", //값 타입 컬렉션 이용 시 사용하는 어노테이션
joinColumns = @JoinColumn(name = "MEMBER_ID"))
@Column(name="FOOD_NAME")
private Set<String> favoriteFoods = new HashSet<>();
@ElementCollection
@CollectionTable(name = "ADDRESS",
joinColumns = @JoinColumn(name = "MEMBER_ID"))
private List<Address> addressHistory = new ArrayList<>();
...
}
@Embeddable
public class Address {
@Column
private String city;
private String street;
private String zipcode;
...
}
Member member = new Member();
//임베디드 값 타입
member.setHomeAddress(new Address("Soonworld","순해수욕장","123-4567"));
//기본값 타입 컬렉션
member.getFavoriteFoods().add("짬뽕");
member.getFavoriteFoods().add("짜장");
member.getFavoriteFoods().add("탕수육");
//임베디드 값 타입 컬렉션
member.getAddressHistory().add(new Address("서울","강남","123-1234"));
member.getAddressHistory().add(new Address("서울","강북","321-4321"));
em.persist(member);
-------------------------------------
실제 DB에 실행되는 SQL
member : INSERT 1번
member.homeAddress : 임베디드 값이므로 회원 테이블 저장 시 포함
member.favoriteFoods : INSERT 3번
member.addressHistory : INSERT 2번
//실행된 실제 SQL
INSERT INTO MEMBER(ID, CITY, STREET, ZIPCODE) VALUES(1, 'Soonworld', '순해수욕장', '123-4567')
INSERT INTO FAVORITE_FOODS(MEMBER_ID, FOOD_NAME) VALUES(1,"짬뽕");
INSERT INTO FAVORITE_FOODS(MEMBER_ID, FOOD_NAME) VALUES(1,"짜장");
INSERT INTO FAVORITE_FOODS(MEMBER_ID, FOOD_NAME) VALUES(1,"탕수육");
INSERT INTO ADDRESS(MEMBER_ID, CITY, STREET, ZIPCODE) VALUES(1, '서울', '강남', '123-1234')
INSERT INTO ADDRESS(MEMBER_ID, CITY, STREET, ZIPCODE) VALUES(1, '서울', '강북', '321-4321')
//값 타입 컬렉션은 영속성 전이(Cascade) + 고아 객체 제거 기능을 필수로 가진다.
값 타입 컬렉션 조회 시 페치전략 (Default LAZY)
값 타입 컬렉션의 제약사항
엔티티 타입
값 타입
값 타입은 정말 값 타입이라고 판단될 때만 사용 해야 한다! 위에 나열한 예제들 처럼 판단 되는 경우에만 사용 하기..!
특히 엔티티와 값 타입을 혼동해서 엔티티를 값 타입으로 만들면 안됨..! 혼동 주의..!