@Entity
@Data
public class Member {
@Id @GeneratedValue
private Long id;
private String name;
private int age;
}
Member에서의 String, int는 값 타입이다.
임베디드 타입(복합 값 타입)
임베디드 타입이란?
JPA에서는 새로운 값 타입을 직접 정의해서 사용하는 것.
@Embaddable과 @Embedded가 있다. 둘다 기본 생성자가 필수이다.
@Embeddable : 값 타입을 정의하는 곳에 표시
@Embedded : 값 타입을 사용하는 곳에 표시
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
private String name;
@Temporal(TemporalType.DATE) java.util.Date startDate;
@Temporal(TemporalType.DATE) java.util.Date endDate;
private String city;
private String street;
private String zipcode;
}
@Entity
public class Member {
@Id @GenratedValue
private Long id;
private String name;
@Embedded Period workPerod;
@Embedded Address homeAddress;
}
@Embeddable
public class Period {
@Temporal(TemporalType.DATE) java.util.Date startDate;
@Temporal(TemporalType.DATE) java.util.Date endDate;
}
@Embeddable
public class Address {
@Column(name="city")
private String city;
private String street;
private String zipcode;
}
임베디드 타입은 엔티티의 값일 뿐이다. 값이 속한 엔티티의 테이블에 매핑한다.
임베디드 타입을 사용하기 전과 후에 매핑하는 테이블이 같다.
즉, 임베디드 타입 덕분에 객체와 테이블을 아주 세밀하게 매핑하는 것이 가능하다.
결론적으로 잘 설계한 ORM 애플리케이션은 매핑한 테이블의 수보다 클래스의 수가 더 많다.
@Entity
public class Member {
@Embedded Address address;
@Embedded PhoneNumber phoneNumber;
}
@Embeddable
public class Address {
private String city;
private String street;
private String zipcode;
@Embedded Zipcode zipcode;
}
@Embbedable
public class Zipcode {
String zip;
String plusFour;
}
@Embbedable
public class PhoneNumber {
String areaCode;
String localNumber;
@ManyToOne PhoneServiceProvider provider
}
@Entity
public class PhoneServiceProvider {
@Id String name;
}
@AttributeOverride : 속성 재정의
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
private String name;
@Embedded Address homeAddress;
@Embedded Address companyAddress;
}
@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;
}
임베디드 타입에 정희한 매핑정보를 재정의할 때 @AttributeOverride를 쓴다.
하지만 너무 많이 사용하면 엔티티코드가 지저분해진다는 단점이 있다.
임베디드 타입과 null
임베디드 타입이 null이면 메ㅐ핑한 컬럼 값은 모두 null이 된다.
member.setAddress(null);
em.persist(member);
회원 테이블의 주소와 관련된 CITY, STREET, ZIPCODE 컬럼 값이 모두 null이 된다.
자바의 기본타입은 값을 대입하는 것만으로도 값이 복사되지만, 임베디트 타입처럼 직접 정의한 값 타입은 객체 타입이다.
객체 타입은 값을 대입하면 항상 참조 값을 전달하기 때문에 address.setCity("NewCity") 처럼 회원1의 address 값을 공유해서 사용 했을때 결과로 둘다 City가 NewCity로 되는것을 볼 수 있다.
어떻게 해결해야할까?
책에서는 두가지 방법을 추천한다.
Clone을 만드는 방법
setter메소드를 모두 제거해서 객체의 값을 수정하지 못하게 만드는 방법, 즉 불변 객체를 만드는 방법이다.
Clone을 만드는 방법
@Embeddable
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class AddressClone implements Cloneable{
@Column
private String city;
private String street;
private String zipcode;
@Override
public Object clone() throws CloneNotSupportedException {
AddressClone clone = (AddressClone) super.clone();
return clone;
}
public AddressClone(String city) {
this.city = city;
}
}
public static void exClone(EntityManager em) throws CloneNotSupportedException {
MemberEmbeddClone member = new MemberEmbeddClone();
MemberEmbeddClone member1 = new MemberEmbeddClone();
member.setUsername("형준");
member1.setUsername("먕준");
AddressClone a = new AddressClone("old");
AddressClone b = (AddressClone) a.clone();
b.setCity("new");
member.setHomeAddress(a);
member1.setHomeAddress(b);
em.persist(member);
em.persist(member1);
em.flush();
}
불변 객체
불변 객체란? 한번 만들면 절대 변경할 수 없는 객체.
왜 만들까? 객체를 불변하게 만들면 값을 수정할 수 없으므로 부작용을 원천 차단할 수 있기 떄문이다.
어떻게 만들까?
@Embeddable
public class Address {
private String city;
protected Address() {}
public Address(String city){this.city=city}
public String getCity() {
return city;
}
//setter는 만들지 않는다.
}
간단한 방법으로 생성자로만 값을 설정하고 수정자를 만들지 않으면 된다.
값 타입의 비교
동일성(Identity) 비교 : 인스턴스의 참조 값을 비교, == 사용
동등성(Equivalence) 비교 : 인스턴스의 값을 비교, equals()사용
값 타입 컬렉션
@ElementCollection, @CollectionTable을 쓰면 된다.
@Entity
public class Member {
@Id @GenratedValue
private Long id;
@Embedded Address homeAddress;
@ElementCollection
@CollectionTalbe(name = "FAVORITE_FOODS",
joinColumns = @JoinColumn(name = "MEMBER_ID"))
@Column(name ="FOOD_NAME")
private Set<String> favoriteFoods = new HashSet<String>;
@ElementCollection
@CollectionTalbe(name = "ADDRESS",
joinColumns = @JoinColumn(name = "MEMBER_ID"))
private List<Address> addressHistory = new ArrayList<Address>();
}
@Embeddable
public class Address {
@Column
private String city;
private String street;
private String zipcode;
}
Member member = new Member();
member.setHomeAddress(new Address("통영", "몽돌해수욕장", "660-123"));
member.getFavoriteFoods().add("짬뽕");
member.getFavoriteFoods().add("짜장");
member.getFavoriteFoods().add("탕수육");
member.getAddressHistory().add(new Address("서울", "강남", "660-123"));
member.getAddressHistory().add(new Address("서울", "강북", "660-123"));
em.persist(member);