값 타입의 비교
•
인스턴스가 달라도 그 안에 값이 같으면 같은 것으로 봐야 한다.
// primitive type 비교
int a = 10;
int b = 10;
System.out.println(a == b);//true
// 임베디드 타입(인스턴스) 비교
Address a = new Address("서울", "AAA", 1000);
Address b = new Address("서울", "AAA", 1000);
System.out.println(a == b);//false
Java
복사
어째서 임베디드 타입의 ==비교는 false가 뜨는것인가?
당연하다. 인스턴스가 다르니 다른객체이기 때문이다.
그럼 어떻게 해야할까?
•
동일성(identity) 비교: 인스턴스의 참조 값을 비교, == 사용
•
동등성(equivalence)비교: 인스턴스의 값을 비교, equals() 사용
•
값 타입은 a.equals(b)를 사용해서 동등성 비교를 해야 한다.
•
값 타입의 equals()메소드를 적절하게 재정의 해준다(주로 모든 필드 사용)
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Address address = (Address) o;
return Objects.equals(city, address.city) &&
Objects.equals(street, address.street) &&
Objects.equals(zipcode, address.zipcode);
}
@Override
public int hashCode() {
return Objects.hash(city, street, zipcode);
}
...
...
...
// 임베디드 타입(인스턴스) 비교
Address a = new Address("서울", "AAA", 1000);
Address b = new Address("서울", "AAA", 1000);
System.out.println(a.equals(b));//true 이제 true가 된다.
Java
복사
값 타입 컬렉션
•
값 타입을 하나 이상 저장할 때 사용
•
@ElementCollection, @CollectionTable 사용
•
데이터베이스는 컬렉션을 같은 테이블에 저장할 수 없다.
•
컬렉션을 저장하기 위한 별도의 테이블이 필요하다.
@ElementCollection
@CollectionTable(name = "FAVORITE_FOOD", joinColumns = @JoinColumn(name="MEMBER_ID")
@Column(name = "FOOD_NAME")
private Set<String> favoriteFoods = new HashSet<>();
@ElementCollection
@CollectionTable(name = "addressHistory", joinColumns = @JoinColumn(name="MEMBER_ID")
@Column(name = "ADDRESS")
private List<Address> addressHistory = new ArrayList<>();
Java
복사
값 타입 컬렉션 사용
•
값 타입 조회 예제
값 타입 컬렉션도 지연 로딩 전략 사용
Member findMember = em.find(Member.class, 1L);
/*
SQL: select MEMBER_ID, street, zipcode, USERNAME from MEMBER
where MEMBER_ID = 1L;
*/
Java
복사
•
값 타입 수정 예제
/*기본적인 임베디드 타입 변경*/
findMember.getHomeAddress().setCity("newCity");// 잘못된 수정
Address add = findMember.getHomeAddress();
findMember.setHomeAddress(new Address("newCity", add.getStreet(), add.getZipcode()));
/*값 타입 컬렉션 수정 예제 - 치킨을 김밥으로 변경*/
findMember.getFavoriteFoods().remove("치킨");
findMember.getFavoriteFoods().add("김밥");
findMember.getAddressHistory().remove(new Address("old1", "street", "10000"));
findMember.getAddressHistory().add(new Address("newCity", "street", "10000"));
Java
복사
값 타입 컬렉션들은 생명주기를 소유객체에 의존한다.
◦
여기서 값 타입 컬렉션 실행 쿼리를 보면 기존 데이터만 삭제하고 신규 데이터를 추가하는 것이 아닌
값타입 컬렉션 데이터 전체가 갈아끼워진다.
•
참고: 값 타입 컬렉션은 영송석 전이(CASCADE)와 고아 객체 제거 기능을 필수로 가진다고 볼 수 있다.
값 타입 컬렉션의 제약사항
•
값 타입은 엔티티와 다르게 식별자 개념이 없다.
•
값은 변경하면 추적이 어렵다.
•
값 타입 컬렉션에서 변경 사항이 발생하면, 주인 엔티티와 연관된 모든 데이터를 삭제하고 값 타입 컬렉션에 있는 현재 값을 모두 다시 저장한다.
•
값 타입 컬렉션을 매핑하는 테이블은 모든 컬럼을 묶어서 기본키를 구성해야한다.
(null 입력x, 중복 저장x)
•
이러한 이유로 실무에선 사용 안하는걸 추천한다.
◦
실무에서는 상황에 따라 값 타입 컬렉션 대신 일대다 관계를 고려한다.
◦
일대다 관계를 위한 엔티티를 만들고, 여기에서 값 타입을 사용
◦
영속성 전이(CASCADE)+ 고아 객체 제거를 사용해서 값 타입 컬렉션처럼 사용
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
@JoinColumn(name = "MEMBER_ID")
private List<AddressEntity> addressHistory = new ArrayList<>();
Java
복사
값 타입 컬렉션 사용 시기
: 정말 단순한 경우
Ex: 좋아하는 음식메뉴 다중 선택과 같이 심플한 자료들.
정리
1.
엔티티 타입의 특징
•
식별자가 있다
•
생명 주기 관리(값 타입은 생명주기 관리를 주도적으로 할 수 없다.)
•
공유
2.
값 타입의특징
•
식별자가 없다
•
생명 주기를 엔티티에 의존
•
공유하지 않는 것이 안전(복사해서 사용)
•
불변 객체로 만드는 것이 안전