엔티티 타입 vs 값 타입
엔티티
값을 변경해도 식별자로 인식 가능.
값
값을 변경하면 그냥 바뀜
값 타입의 종류
기본값 타입
- int, double, Integer, Long, String
- int는 공유가 안되는데 Integer 같은 래퍼 클래스는 참조로 가져가기 때문에 값을 변경하면 다른것도 바뀜 그렇지만 값이 변경이 안되어서 괜찮다.
임베디드 타입
- 객체로 값타입들을 모은 타입 (복합 값 타입)
- @Embeddable : 임베디드 타입을 정의 한 곳에. (기본 생성자를 만들어주고 사용해야한다.)
- @Embedded : 임베디드 타입을 사용할 곳에
- 임베디드 타입을 사용해도 매핑되는 테이블은 똑같다.
- 정의되는 곳에 엔티티를 넣을 수도 있다.
- 같은 값 타입을 중복해서 사용할 때는 @AttributeOverrides를 사용한다.
- 같은 값타입을 쓰는 다른 엔티티에서 값타입을 수정하면 다른 엔티티도 바뀌게된다.
그래서 값타입을 불변객체로 만들어야한다. -> Setter를 만들지 않는다.
값 타입 비교
동일성 비교 : 참조 값을 비교, ==
동등성 비교 : 인스턴스값 비교, equals()
equals() 사용할때는 equals()를 오버라이드 받아 구현해주어야한다.
(인텔리제이에서 만들어주는 오버라이드 equals()를 그대로 사용하면 된다.)
- command + N
- equals() and hasgCode() 클릭
getter를 사용하도록 체크해야 프록시를 사용하더라도 equals() 가 작동한다.
값 타입 컬렉션
값 타입을 하나 이상 저장할 때 사용
값 타입 컬렉션은 각 값타입들을 다 묶어서 PK로 설정해주어야 한다.
값 타입 컬렉션 사용
- @ElementCollection
- @CollectionTable
name : 테이블 이름 지정
joinColumns = @JoinColumn(name = "MEMBER_ID") : FK 매핑 - Embedded 타입이 아닌경우는 Column명 설정 가능
@Column(name = "FOOD_NAME")
굳이 이렇게 사용하는 이유
데이터베이스에서는 컬렉션을 같은 테이블에 저장할 수 없다.
그래서 일 대 다 개념으로 사용하기 때문에 FK매핑이 필요해진다.
저장
코드
결과
값 타입들은 엔티티에 의존하기 때문에 em.persist(member); 한번으로도 값 타입들의 insert 쿼리들이 한번에 날아간다.
( = 값 타입 컬렉션도 지연 로딩 전략을 사용한다.)
값 타입 컬렉션의 지연로딩 전략
findMember를 생성할 시점에는 List, Set (컬렉션) 타입의 데이터를 가져오지 않았다가 사용시점에 insert쿼리가 날아가는 것을 볼 수 있다.
@ElementCollection의 default 가 fetch LAZY 이다.
값 타입 수정
findMember.getHomeAddress().setCity("newCity");
-> 안된다. side effect 발생.
Address homeAddress = findMember.getHomeAddress();
findMember.setHomeAddress(new Address("newCity", homeAddress.getStreet(), homeAddress.getZipcode()));
이런 식으로 값 타입을 새로 만들어서 넣어줘야한다.
값 타입 컬렉션 수정
Set
member.getFavoriteFoods().add("치킨");
member.getFavoriteFoods().add("족발");
member.getFavoriteFoods().add("피자");
findMember.getFavoriteFood() -> Set<String> 형식이다.
여기서 "치킨" 을 "한식"으로 바꾸려고 할때도 값 타입 수정에서의 원리를 적용해야한다.
왜냐하면 String도 값타입이기 때문에 "수정"을 못하는 "불변의 법칙"이 적용되어있기 때문이다.
수정방법
findMember.getFavoriteFoods().remove("치킨");
findMember.getFavoriteFoods().add("한식");
List
findMember.getAddressesHistory().remove(new Address("old1", "street1", "10000"));
findMember.getAddressesHistory().add(new Address("newCity1", "street1", "10000"));
new Address 자체를 넣어서 찾은다음에 삭제시키고, 새로 new Address를 넣어준다.
new Address 자체를 넣어 찾기때문에 equals와 hashcode가 중요해진다.
실무에서 값타입 컬렉션의 대안
일대다 관계 엔티티를 만들고 여기에 값 타입을 넣는다.
영속성 전이(Cascade) 와 orphanRemover를 사용해서 값 타입 컬렉션처럼 사용한다.
AddressEntity
package hellojpa;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name = "ADDRESS")
public class AddressEntity {
@Id @GeneratedValue
private Long id;
private Address address;
public AddressEntity(String city, String street, String zipcode) {
this.address = new Address(city, street, zipcode);
}
public AddressEntity(Address address) {
this.address = address;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
}
Member
...
@OneToMany(cascade = ALL, orphanRemoval = true)
@JoinColumn(name = "MEMBER_ID")
private List<AddressEntity> addressesHistory = new ArrayList<>();
...
JpaMain
값 넣기
...
member.getAddressesHistory().add(new AddressEntity("old1", "street1", "10000"));
member.getAddressesHistory().add(new AddressEntity("old2", "street1", "10000"));
...
값 수정하기
... 방법 모르겠음
정리
엔티티 타입 특징
- 식별자가 필요하다
- 생명주기 관리가 가능하다
- 공유가 가능하다
값 타입 특징
- 식별자가 없다
- 생명주기를 엔티티에 의존한다.
- 공유하지 않고 값을 복사해서 넣어야 한다.
(이때 혹시 공유해 사용될 수 있어서 사이드이펙트를 방지하기 위해 불변객체로 만든다.) - 정말 값 타입이라고 판단될 때 사용한다.
(식별자가 필요하고 값을 추적, 변경해야 한다면 엔티티로 만든다.)
'Web > JPA' 카테고리의 다른 글
EP11. JPQL소개, 기본문법 (0) | 2021.02.19 |
---|---|
CascadeType.REMOVE vs orphanRemoval = true (0) | 2021.02.13 |
EP9. 프록시와 연관관계 관리 (0) | 2021.02.10 |
EP8. 고급 매핑 (0) | 2021.02.09 |
EP7. 다양한 연관관계 매핑 (0) | 2021.02.08 |