@Entity
로 정의하는 객체@Embeddable
: 값 타입을 정의하는 곳에 표시한다.@Embedded
: 값 타입을 사용하는 곳에 표시한다.public
기본 생성자가 필수이다.재사용
과 높은 응집도
가 장점이다.@Entity
public class Member {
@Id @GeneratedValue
private Long id;
@Embedded
private Address homeCity;
@Embedded
@AttributeOverrides({ // ** 재정의!!
@AttributeOverride(name = "city",
column = @Column("WORK_CITY")),
@AttributeOverride(name = "street",
column = @Column("WORK_STREET")),
@AttributeOverride(name = "zipcode",
column = @Column("WORK_ZIPCODE"))
})
private Address officeCity;
// getter, setter ...
}
...
Address address = new Address("city", "street", "10000");
Member member = new Member();
member.setUsername("member1");
member.setHomeAddress(address);
em.persist(member);
Member member2 = new Member();
member2.setUsername("member2");
member2.setHomeAddress(address); // 인스턴스 공유 중
em.persist(member2);
// 이때 만약 member의 주소를 바꾼다면?
member.getHomeAddress.setCity("newCity");
em.persist(member);
...
...
Address address = new Address("city", "street", "10000");
Member member = new Member();
member.setUsername("member1");
member.setHomeAddress(address);
em.persist(member);
Address address2 = new Address(address.getCity(), address.getStreet(), address.getZipcode());
Member member2 = new Member();
member2.setUsername("member2");
member2.setHomeAddress(address2); // 값은 같지만 주소값은 다른 새로운 인스턴스
em.persist(member2);
member.getHomeCity().setCity("newCity");
...
@ElementCollection
, @CollectionTable
어노테이션을 사용한다.// Member.java
...
@ElementCollection
@CollectionTable(name = "FAVORITE_FOOD",
joinColumns = JoinColumn(name = "MEMBER_ID"))
@Column(name = "FOOD_NAME") //** String에 한 해서 컬럼명 지정 가능
private Set<String> favoriteFoods = new HashSet<>();
@ElementCollection
@CollectionTable(name = "ADDRESS",
joinColumns = JoinColumn(name = "MEMBER_ID"))
private List<Address> addressHistory = new ArrayList<>();
...
...
Address address = new Address("city", "street", "10000");
Member member = new Member();
member.setUsername("member1");
member.setHomeAddress(address);
member.getFavoriteFoods().add("치킨");
member.getFavoriteFoods().add("피자");
member.getAddressHistory().add(new Address("old1", "street", "10000"));
em.persist(member);
Member findMember = em.find(Member.class, member.getId());
// 조회 시 member 테이블 조회만 이뤄진다.
// ADDRESS, FAVORITE_FOODS은 자동으로 LAZY Fetch가 적용된다.
...
INSERT INTO FAVORITE_FOOD (MEMBER_ID, FOOD_NAME) VALUES (1, '치킨');
INSERT INTO FAVORITE_FOOD (MEMBER_ID, FOOD_NAME) VALUES (1, '피자');
INSERT INTO ADDRESS (MEMBER_ID, CITY, STREET, ZIPCODE) VALUES (1, 'old1', 'street', '10000);
INSERT INTO MEMBER (USER_NAME, HOME_ADDRESS_ID) VALUES ('member1', '1');
값 타입 컬렉션은 영속성 전이(Cascade) + 고아 객체 제거 기능을 필수로 가진다고 볼 수 있다.
@Entity
public class AddressEntity {
@Id @GeneratedValue
private Long id;
private Address address;
// getter, setter ..
}
// Member.java
// @ElementCollection
// @CollectionTable(name = "ADDRESS",
// joinColumns = JoinColumn(name = "MEMBER_ID"))
// private List<Address> addressHistory = new ArrayList<>();
@OneToMany(fetch = Lazy, cascade = CascadeType.ALL, orphanRemoval = true)
@JoineColoumn(name = "MEMBER_ID")
private List<AddressEntity> addressHistory;
값 타입(VO)은 기본키로 식별되는 엔티티와 달리 순수한 값을 표현하고자할 때 사용한다. 값 타입이 공유될 경우 값이 변경되면 부작용이 발생하므로 모든 값 타입은 불변 객체로 만들어야 한다.
하나의 엔티티에 여러개의 값타입을 참조해야할 경우, 값 타입 컬렉션을 사용할 수 있다. 하지만 값타입 컬렉션을 사용할게 될 경우 값의 변경 시 주인 엔티티와 연관된 모든 데이터를 지우고 다시 저장하므로 비효율이 발생하게된다. 따라서 실무에서는 값 타입 컬렉션을 사용해야 하는 경우 엔티티로 만들어서 일대다 관계로 부모엔티티가 생명주기를 관리하게 하는 것이 현명할 것이다.