JPA에서는 엔티티 타입과 값 타입으로 데이터 타입을 분류한다.
@Entity
로 정의하는 객체int, Integer, String
처럼 단순히 값으로 사용하는 자바 기본 타입, 객체값 타입에는 기본값 타입, 임베디드 타입, 컬렉션 값 타입이 있다.
새로운 값 타입을 직접 정의할 수 있다. 주로 기본 값 타입을 모아서 만들기 때문에 복합 값 타입이라고도 한다. 엔티티가 아닌 값 타입이다.
클래스를 새로 뽑는 것과 같다. @Embeddable
은 값 타입을 정의하는 곳에 표시하고 @Embedded
는 값 타입을 사용하는 곳에 표시한다.
기본 생성자가 있어야 한다.
@Embeddable
public class Period {
private LocalDateTime startDate;
private LocalDateTime endDate;
}
값을 정의하는 곳에는 @Embeddable
@Embedded
private Period workPeriod;
값을 사용하는 곳에는 @Embedded
임베디드 타입을 사용하기 전과 후 매핑하는 테이블은 같다
객체와 테이블을 아주 세밀하게 매핑하는 것이 가능하다
임베디드 타입이 임베디드 타입을 필드로 가질 수 있다
임베디드 타입이 엔티티를 필드로 가질 수 있다
한 엔티티에 주소가 두 개 있다면?
@AttributeOverides, @AttributeOveride
를 사용해서 컬럼 명 속성을 재정의 할 수 있다.
임베디드 타입 값 자체가 null이면 해당 값들은 모두 null이 된다.
임베디드 타입 같은 값 타입을 여러 엔티티에서 공유하면 side effect가 발생할 수 있다.
같이 공유하고 싶으면 값 타입이 아닌 엔티티를 사용하는게 맞다
임베디드 타입을 사용할 경우 기존 임베디드 타입 값을 복사해서 새로 객체를 만들어서 사용하는 게 맞다.
기본 타입은 int b=a
하면 값을 복사함
객체 타입은 Address b=a
하면 참조를 전달함
해결 방법 : 값 타입을 불변 객체로 만든다
값 타입은 인스턴스가 달라도 그 안의 값이 같으면 같은 것으로 봐야 한다.
자바에서는 ==
비교가 참조값을 비교하므로 값이 같은 다른 객체를 생성하면 다른 것으로 비교함
동일성 비교와 동등성 비교를 구분해서 사용해야 한다.
==
비교. 인스턴스의 참조 값을 비교equals()
사용. 인스턴스의 값을 비교 equals()
는 ==
비교를 하기 때문에 오버라이딩을 해줘야 한다.equals()
를 오버라이딩 해서 사용하자값 타입을 컬렉션에 담아서 사용하는 것이다.
관계형 데이터베이스는 컬렉션을 DB에 넣을 수가 없다.
이런 식으로 일대 다 관계로 별도의 테이블을 뽑아야 한다.
즉 컬렉션을 저장하기 위한 별도의 테이블이 필요하다.
@ElementCollection
@CollectionTable(name = "FAVIROTE_FOOD",
joinColumns = @JoinColumn(name="MEMBER_ID")
)
private Set<String> favoriteFood = new HashSet<>();
@ElementCollection
@CollectionTable(name="PERIODS",
joinColumns = @JoinColumn(name="MEMBER_ID")
)
private List<Period> periods = new ArrayList<>();
값 타입 컬렉션인 경우 @ElementCollection
을 붙여줘야 함
@CollectionTable
로 테이블 명을 지정해 줘야 함
joinColumns로 매핑해줘야 한다.
값 타입은 별도로 업데이트 할 필요 없이 Member의 값을 업데이트 하면 알아서 반영된다.
컬렉션들은 다 지연 로딩으로 설정되어있다.
값 타입은 immutable 해야 하기 때문에 setter를 사용하지 않는다.
새로운 값 타입 객체를 만들어서 새로 넣는게 맞다.
member.getHomeAddress().setCity("newCity"); // setter 사용 X!
member.setHomeAddress(new Address("newCity",..,..)); // 새롭게 값 타입을 넣어줘야 한다
값 타입 컬렉션 업데이트는 아래와 같이 한다
findMember.getFavoriteFoods().remove("치킨");
findMember.getFavoriteFoods().add("한식");
findMember.getAddressHistory().remove(new Address("oldCity"));
findMember.getAddressHistory().add(new Address("newCity"));
컬렉션의 경우 remove로 삭제할 때 equals로 비교해서 찾기 때문에 equals의 해시코드를 제대로 구현해 놓아야 한다.
값 타입 컬렉션에 변경 사항이 발생하면 주인 엔티티와 관련된 모든 데이터를 삭제하고, 값 타입 컬렉션에 현재 있는 값을 모두 다시 저장한다.
값 타입 컬렉션을 매핑하는 테이블은 모든 컬럼을 묶어서 기본 키를 구성한다.
(null X, 중복 저장 X)
실무에서는 값 타입 컬렉션 대신 일대다 관계를 고려하는게 좋다
일대다 관계를 위한 엔티티를 만들고, 엔티티에서 값 타입을 사용한다.
영속성 전이(cascade all) + 고아 객체를 사용해서 값 타입 컬렉션처럼 사용하는게 좋다.
@OneToMany(cascade=CascadeType.ALL, orphanRemoval=true)
@JoinColumn(name="member_id")
private List<Period> period;
진짜 단순한 경우가 아니면 값 타입 컬렉션을 사용하지 않음
지속해서 값을 추적, 변경해야 한다면 그것은 값 타입이 아닌 엔티티이다.