임베디드 타입(복합 값 타입)
@Entity
public class Member {
@Id
@GeneratedValue
private Long id;
private String name;
@Embedded
private Address homeAddress;
@Embedded
@AttributeOverrides({
@AttributeOverride(name = "city", column=@Column(name = "company_city")),
@AttributeOverrdie(name = "district", column=@Column(name = "company_district")),
@AttributeOveerdie(name = "zipcode", column=@Column(name = "company_zipcode"))})
private Address companyAddress;
}
@Embeddable
public class Address {
@Column(name = "city")
private String city;
@Column(name = "district")
private String district;
@Embedded
private Zipcode zipcode;
}
@Embeddable
public class Zipcode {
@Column(name = "zip")
private String zip;
@Column(name = "plus_four")
private String plusFour;
}
다음 코드와 같이 주소 클래스를 따로 만들어 구현하면 응집도가 높아지고 객체지향적으로 바뀔 수 있다.
- @Embeddable : 값 타입을 정의하는 곳에 표시
- @Embedded : 값 타입을 사용하는 곳에 표시
- @AttributeOverride : 속성 재정의
- 임베디드 타입에 정의한 매핑정보를 재정의할 때 사용한다.
- 똑같은 임베디드 타입이라면 컬럼명이 중복되므로 매핑정보를 재정의할 수 있다.
- 임베디드 타입이 null이면 매핑한 컬럼 값은 모두 null이 된다. 즉, member.setHomeAddress(null); 을 하면 연관된 컬럼 값은 모두 null이다.
값 타입과 불변 객체
임베디드 타입 같은 값 타입을 여러 엔티티에서 공유하면 안된다.
member1.setHomeAddress(new Address("OldCity"));
Address address = member1.getHomeAddress();
address.setCity("NewCity");
member2.setHomeAddress(address);
member2의 주소만 "NewCity"로 변경되길 기대했지만 member1의 주소도 "NewCity"로 변경된다.
member1과 member2가 같은 address 인스턴스를 참조했기 때문에 영속성 컨텍스트는 member1과 member2 둘 다 city 속성이 변경되었다고 판단하여 각각 update SQL을 실행한다.
따라서 가장 좋은 방법은 불변 객체를 만드는 것 이다.
객체를 불변하게 만들면 값을 수정할 수 없으므로 부작용을 원천 차단할 수 있다.
즉, 객체를 만들 때 Setter를 만들지 않고, 생성자를 통해서만 생성할 수 있게 하면 된다.
값 타입 컬렉션
@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<String>();
@ElementCollection
@CollectionTable(name = "address",
joinColumns = @JoinColumn(name = "member_id"))
private List<Address> addressHistory = new ArrayList<Address>();
}
@Embeddable
public class Address {
@Column(name = "city")
private String city;
@Column(name = "district")
private String district;
}
- 값 타입을 하나 이상 저장하려면 컬렉션에 보관하고 @ElementCollection, @CollectionTable 어노테이션을 사용하면 된다.
- 값 타입 컬렉션에 보관된 값 타입들은 별도의 테이블에 보관된다.
따라서, 보관된 값 타입이 변경되면 데이터베이스에 있는 원본 데이터를 찾기 어렵다는 문제가 발생한다.
따라서 JPA 구현체들은 값 타입 컬렉션에 변경 사항이 발생하면, 값 타입 컬렉션이 매핑된 테이블의 연관된 모든 데이터를 삭제하고, 현재 값 타입 컬렉션 객체에 있는 모든 값을 데이터베이스에 다시 저장한다.
정리
엔티티 타입의 특징
- 식별자가 있다.
-> 엔티티 타입은 식별자가 있고 식별자로 구분할 수 있다. - 생명 주기가 있다.
-> 생성하고, 영속화하고, 소멸하는 생명 주기가 있다. - 공유할 수 있다.
-> 참조 값을 공유할 수 있다.
값 타입의 특징
- 식별자가 없다.
- 생명 주기를 엔티티에 의존한다.
-> 스스로 생명주기를 가지지 않고 엔티티에 의존한다. 즉, 의존하는 엔티티를 제거하면 같이 제거된다. - 공유하지 않는 것이 안전하다.
-> 엔티티 타입과는 다르게 공유하지 않는 것이 안전하다. 대신에 값을 복사해서 사용해야 한다.
-> 오직 하나의 주인만이 관리해야 한다.
-> 불변 객체로 만드는 것이 안전하다.
'Spring JPA' 카테고리의 다른 글
객체 지향 쿼리 언어 - 14 (0) | 2021.07.21 |
---|---|
객체지향 쿼리 언어 - 13 (0) | 2021.07.17 |
프록시와 연관관계 관리 - 11 (0) | 2021.07.13 |
고급 매핑 - 10 (0) | 2021.07.12 |
다양한 연관관계 매핑 - 9 (0) | 2021.07.09 |
댓글