Hibernate(7) - 값 타입
1. JPA의 데이터 타입 분류
1.1. 엔티티 타입
- @Entity로 정의하는 객체
- 데이터가 변해도 식별자로 지속해서 추적 가능 ex) 회원 나이를 변경해도 식별자로 추적 가능
1.2. 값 타입
- int, Integer, String 처럼 단순히 값으로 사용하는 자바 기본 자료형이나 객체
- 식별자가 없고 값만 있으므로 변경 시 추적 불가 ex) 회원 나이를 20에서 21로 변경하면 완전히 다른 값으로 대체
- 분류
> 기본값 타입 : primprimative type, Wrapper Class, String
> 임베디드 타입 : embedded type, 복합 값 타입
> 컬렉션 값 타입
2. 타입 별 특징
2.1. 기본 값 타입
- 생명주기를 엔티티에 의존
- 값타입이 공유되어서는 안된다. ex) 회원A의 나이를 수정했을 때, 회원B의 나이가 수정되어서는 안된다.
2.2. 임베디드 타입(복합 값 타입)
- 여러 엔티티에서 공유하면 부작용(side effect)이 발생할 확률이 높으므로 지양
> 값을 복사해서 사용하도록
> 불변객체로 설계하면 부작용을 차단할 수 있음
- 생성자로만 값을 설정하고, 수정자(Setter)를 만들지 않으면 됨
- 값 타입을 소유한 엔티티에 생명 주기를 의존함
- 새로이 정의한 값 타입
- 주로 기본 값 타입을 모아 만들기 때문에 복합 값 타입이라고도 함
- 엔티티가 아니기 때문에 추적할 수 없다.
- 객체와 테이블을 아주 세밀하게 매핑할 수 있다.
- 사용 방법
> 기본 생성자 필수
> @Embeddable : 값 타입을 정의하는 곳에 표시
> @Embedded : 값 타입을 사용하는 곳에 표시
- 한 엔티티에서 같은 값 타입을 두 번 이상 사용하면 컬럼 명이 중복됨
> @AttributeOverride, @AttributeOverrides 활용
- 임베디드 타입 변수 자체가 null이면 임베디드 타입이 갖는 모든 변수는 null
package jpql;
import lombok.Getter;
import javax.persistence.Embeddable;
@Embeddable
@Getter
public class Address {
private String city;
private String street;
private String zipcode;
}
package jpql;
import lombok.Getter;
import lombok.Setter;
import javax.persistence.*;
@Entity
@Table(name = "ORDERS")
@Getter @Setter
public class Order {
@Id @GeneratedValue
private Long id;
private int orderAmount;
@Embedded
private Address address;
@ManyToOne
@JoinColumn(name = "PRODUCT_ID")
private Product product;
}
3. 값 타입의 비교
3.1. 동일성(identity) 비교 : 인스턴스의 참조 값을 비교
- == 사용
3.2. 동등성(equivalence) 비교 : 인스턴스가 갖는 값들을 비교
- equals()와 hashCode() 메소드를 적절히 오버라이딩 해서 사용
- equals() 오버라이딩에는 주로 모든 필드 사용
4. 값 타입 컬렉션
4.1. 엔티티가 컬렉션 변수를 가질 때(List, Set)
- DB에서 각 컬렉션 변수는 각각의 테이블을 갖는다
- @ElementCollection, @CollectionTable 사용
4.2. 값 타입 컬렉션 활용
- 수정 쪽 문제 때문에 값 타입 컬렉션 대신에 일대다 관계를 활용하는게 이득
> Cascade + 고아 객체 제거 기능을 사용해서 값 타입 컬렉션처럼 사용
> 값 타입(Address.java)을 엔티티(AddressEntity.java)로 승급
//값 타입 컬렉션 대신에 엔티티 일대다 관계 활용
@OneToMany(orphanRemoval = true, cascade = CascadeType.ALL)
@JoinColumn(name = "MEMBER_ID")
private List<AddressEntity> addressHistory = new ArrayList<>();
- 저장
> 영속상태인 엔티티가 갖는 컬렉션에 add하고 커밋하면 끝
- 조회
> 엔티티를 조회할 때는 컬렉션 값을 제외한 엔티티 내용만 가져옴
- 컬렉션 값은 사용될 때 쿼리 수행됨 (지연 로딩)
- 수정
> 수정 대상을 삭제(remove)하고 새(new) 값을 add
- 이렇게 하면 컬렉션 사이즈 만큼 딜리트, 인서트 쿼리가 수행됨
- 값 타입 컬렉션에 변겨여 사항이 발생하면, 주인 엔티티와 연관된 모든 데이터를 삭제하고, 값 타입 컬렉션에 있는 현재 값을 모두 다시 저장한다. 값 타입에는 식별자 개념이 없고 추적이 어렵기 때문
- 아주 위험하다.
4.3. 참고
- 값 타입 컬렉션은 영속성 전이(Cascade) + 고아 객체 제거 기능을 필수로 가진다고 볼 수 있다.
> 엔티티가 갖는 컬렉션의 생명주기는 엔티티에 종속되어 있기 때문