int a = 20;
int b = a;
b = 10;
result > a = 20, b = 10;
Integer a = new Integer(10);
Integer b = a;
a.setValue(20);
result > a = 20, b = 20; // side Effect !
@Entity
public class Member {
@Id
@GeneratedValue
private Long id;
private String name;
@Embedded
Address homeAddress;
...
}
@Embeddable
public class Address {
@Column (name = "city")
private String city;
private String street;
private String zipcode;
...
}
엔티티를 더욱 의미있고 응집력 있게 변화시킬 수 있다 ! 재사용도 가능하다 !
임베디드를 포함한 모든 값 타입은 엔티티 생명주기에 의존 !
엔티티와 임베디드 타입( = 컴포넌트)의 관계를 UML로 표현하면 컴포지션 관계가 된다.
컬럼명이 중복되어 MappingException : Repeated column
에러 발생 !
아래와 같이 @AttributeOverrides
와 @AttributeOverride
를 통해 속성을 재정의하면 된다
@Entity
public class Member {
...
@Embedded
@AttributeOverrides({
@AttributeOverride(name = "city", column = @Column(name = "WORK_CITY"),
...
private Address address;
...
}
임베디드 타입이 null 이면 매핑한 컬럼 값은 모두 null이 된다
임베디드 같은 값 타입을 여러 엔티티에서 공유하면 위험하다.
member1.setHomeAddress(new Address("OldCity"));
Address address = member1.getHomeAddress();
address.setCity("NewCity"); // 회원 1의 address 값을 공유해서 사용
member2.setHomeAddress(address);
이와같은 공유 참조로 인해 발생하는 Side Effect는 찾기 어렵다 ! 이를 막기위해서는 값 타입을 복사해서 사용해야 한다
자바는 대입하려는 것이 값 타입인지 아닌지는 신경쓰지 않고 자바 기본 타입이면 값을 복사해서 넘기고 객체면 참조해서 넘긴다. 복사하지 않고 원본의 참조 값을 직접넘기는 것을 막을 방법이 없다.
Address a = new Address("OldCity");
Address b = a.clone(); // 항상 복사해서 넘겨야 공유되지 않는다 !!
b.setCity("New");
result > a.city = "OldCity" , b.city = "New"
객체의 공유참조는 피할 수 없다. 가장 근본적인 해결책은 set 같은 수정자 메소드를 모두 제거하는것
객체를 불변하게 만들면 값을 수정할 수 없으므로 부작용을 완전 차단할 수 있다.
값 타입은 될 수 있으면 불변 객체로 설계해야 한다.
setter를 쓰지 않고 객체를 통으로 변경해야 한다.
값 타입을 하나 이상 저장하려면 컬렉션에 보관하고
@ElementCollection
, @CollectionTable
어노테이션을 사용하면 된다.
@Entity
public class Member {
@Id
@GeneratedValue
private Long id;
@ElementCollection
@CollectionTable(name = "FAVORITE_FOODS",
joinColumns = @JoinColumn(name = "MEMBER_ID"))
@Column(name = "FOOD_NAME")
private Set<String> favoriteFoods = new HashSet<String>();
@ElementCollection
@CollectionTable(name = "ADDRESS", joinColumns
= @JoinColumn(name = "MEMBER_ID"))
private List<Address> addressHistory = new ArrayList<Address>();
...
}
🔊 `@CollectionTable` 을 생략하면 기본 값을 사용해서 매핑
**기본 값** : {엔티티 이름}_{컬렉션 속성 이름}.
Member member = new Member();
//임베디드 값 타입
member.setHomeAddress(new Address("통영", "몽돌해수욕장","660-123");
//기본값 타입 컬렉션
member.getFavoriteFoods().add("짬뽕");
member.getFavoriteFoods().add("짜장");
member.getFavoriteFoods().add("탕수육");
//임베디드 값 타입 컬렉션
member.getAddressHistory().add(new Address("서울","강남", "123-123"));
member.getAddressHistory().add(new Address("서울","강북","000-000"));
em.persist(member);
실제 DB에 실행되는 Insert SQL은 다음과 같다.
값 타입 컬렉션도 조회할 때 페치 전략을 선택가능, default = LAZY
Member member = em.find(Member.class, 1L);
//1. 임베디드 값 타입 수정
member.setHomeAddress(new Address("새로운도시", "신도시1","123456");
//2. 기본값 타입 컬렉션 수정
Set<String> favoriteFoods = member.getFavoriteFoods();
member.getFavoriteFoods().remove("탕수육");
member.getFavoriteFoods().add("치킨");
//3. 임베디드 값 타입 컬렉션 수정
List<Address> addressHistory = member.getAddressHistory();
addressHistory.remove(new Address("서울","기존주소", "123-123"));
addressHistory.add(new Address("새로운도시","새로운주소","000-000"));