////
Search
Duplicate
🕙

9장. 값 타입

JPA의 데이터 타입을 가장 크게 분류하면 엔티티 타입과 값 타입으로 나눌 수 있다.
엔티티 타입은 @Entity로 정의하는 객체이고 값 타입은 int, Integer, String처럼 단순히 값으로 사용하는 원시형, 참조형 타입을 의미한다.
값 타입은 식별자가 없고 숫자나 문자같은 속성만 있으므로 추적할 수 없으며 다음과 같이 셋으로 분류할 수 있다.
기본값 타입
자바 기본 타입
래퍼 클래스
String
임베디드 타입
컬렉션 값 타입

1. 기본값 타입

@Entity public class Member { @Id @GeneratedValue private Long id; private String name; // 기본값 타입 private int age; // 기본값 타입 }
Java
복사
Member 엔티티는 id라는 식별자 값도 가지고 생명주기도 있지만 값 타입인 name, age 속성은 식별자 값도 없고 생명주기도 회원 엔티티에 의존한다.

2. 임베디드 타입(복합 값 타입)

새로운 값 타입을 직접 정의해서 사용할 수 있으며 이를 임베디드 타입이라고 한다.
@Entity public class Member { @Id @GeneratedValue private Long id; ... @Embedded Period workPeriod; @embedded Address homeAddress; } @Embeddable public class Period { @Temporal(TemporalType.DATE) Date startDate; @Temporal(TemporalType.DATE) Date endDate; } @Embeddable public class Address { @Column (name="city) private String city; private String street; private String zipCode; }
Java
복사
@Embeddable, @Embedded을 이용해 임베디드 타입을 설정하였다. 참고로 임베디드 타입은 기본 생성자가 필수다.
임베디드 타입을 포함한 모든 값 타입은 엔티티의 생명주기에 의존하므로 엔티티와 임베디드 타입의 관계를 UML로 표현하면 구성 관계가 된다.

1. 임베디드 타입과 테이블 매핑

그렇다면 임베디드 타입을 테이블에 어떻게 매핑할까?
임베디드 타입은 엔티티의 값일 뿐으로 값이 속한 엔티티의 테이블에 매핑한다.

2. 임베디드 타입과 연관관계

임베디드 타입은 값 타입을 포함하는 기능 외에도 엔티티를 참조할 수 있다.

3. @AttributeOverride: 속성 재정의

임베디드 타입에 정의한 매핑정보를 재정의하려면 엔티티에 @AttributeOverride를 사용하면 된다.
동일한 임베디드 타입을 재사용하게되는 경우, 테이블에 매핑하는 컬럼명이 중복될 것이다. 이때 @AttributeOverride를 사용해서 컬럼명들을 매핑해주어야 한다.

4. 임베디드 타입과 null

임베디드 타입이 null이면 매핑한 컬럼 값은 모두 null이 된다.

3. 값 타입과 불변 객체

1. 값 타입 공유 참조

임베디드 타입 같은 값 타입을 여러 엔티티에서 공유하면 문제가 발생할 가능성이 매우 높다.

2. 값 타입 복사

값 타입의 실제 인스턴스인 값을 공유하는 것이 위험하므로 대신 값을 복사해서 사용해야 한다.

3. 불변 객체

값 타입은 사이드 이펙트 걱정 없이 사용할 수 있어야 한다. 객체를 불변하게 만들면 값을 수정할 수 없으므로 부작용을 원천 차단할 수 있다.
값 타입은 될 수 있으면 불변 객체로 설계해야 한다.

4. 값 타입의 비교

값 타입은 비록 인스턴스가 달라도 그 안의 값이 같다면 같은 것으로 봐야하므로 동등성 비교를 해야 한다.
따라서 값 타입 내부에 hashCode(), equals() 메소드를 적절히 재정의하여 동등성 비교를 수행할 수 있도록 해야 한다.

5. 값 타입 컬렉션

값 타입을 하나 이상 저장하려면 컬렉션에 보관하고 @ElementCollection, @CollectionTable 어노테이션을 사용하면 된다.
@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 private String city; private String street; private String zipCode; ... }
Java
복사

1. 값 타입 컬렉션 사용

값 타입 컬렉션도 조회 시 페치 전략을 선택할 수 있으며 LAZY가 기본이다. 또한 영속성 전이 + 고아 객체 제거 기능을 필수로 가진다고 볼 수 있다.

2. 값 타입 컬렉션의 제약사항

엔티티는 식별자가 있으므로 엔티티의 값을 변경해도 식별자로 데이터베이스에 저장된 원본 데이터르 쉽게 찾아 변경할 수 있다.
그러나 값 타입은 식별자라는 개념이 없고 단순한 값들의 모음이므로 값을 변경해버리면 데이터베이스에 저장된 원본 데이터를 찾기 어렵다.
특정 엔티티 하나에 소속된 값 타입은 값이 변경되어도 자신이 소속된 엔티티를 데이터베이스에서 찾아 값을 변경하면 된다.
문제는 값 타입 컬렉션이다. 값 타입 컬렉션에 보관된 값 타입들은 별도의 테이블에 보관된다. 따라서 여기에 보관된 값 타입의 값이 변경되면 데이터베이스에 있는 원본 데이터를 찾기 어렵다.
이런 문제로 인해 JPA 구현체들은 값 타입 컬렉션이 매핑된 테이블의 연관된 모든 데이터를 삭제하고 현재 값 타입 컬렉션 객체의 모든 값을 데이터베이스에 다시 저장하는 식으로 해결한다.
엔티티를 만들어서 연관관계를 매핑해주는 게 더 나을 것 같다.

6. 정리

엔티티 타입의 특징

식별자가 있다.
생명 주기가 있다.
공유할 수 있다.

값 타입의 특징

식별자가 없다.
생명주기가 엔티티에 종속되어 있다.
공유하지 않는 것이 안전하다.