값 타입
JPA의 데이터 타입을 가장 크게 분류하면 엔티티 타입과 값 타입으로 나눌 수 있다.
엔티티 타입은 @Entity로 정의하는 객체이고, 값 타입은 int, Integer, String 처럼 단순히 값으로 사용하는 자바 기본 타입이나 객체를 말한다.
값 타입은 다음 3가지로 나눌 수 있다.
● 기본값 타입(basic value type)
◎ 자바 기본 타입(int, double 등)
◎ 래퍼 클래스(Integer 등)
● 임베디드 타입(embedded type)
● 컬렉션 값 타입(collection value type)
1. 값 타입
■ 값 타입의 특징
기본 값 등을 포함한 값 타입의 특징은 다음과 같다.
● 값 타입은 결국 엔티티에 생명주기를 의존한다.
ex) Member라는 엔티티가 id, name, age를 필드로 갖고 있을 때 Member를 삭제하면 값 타입인 id, name, age 등 도 다 같이 제거된다.
● 값 타입은 식별자 값도 없기 때문에 별개로 값 타입만을 추적할 수는 없다.
● 값 타입은 절대 공유하면 안된다.
ex) 특정 회원 엔티티의 이름을 변경한다고 다른 회원의 이름까지 변경되서는 안 된다.
값 타입은 정말 값 타입이라 판단될 때만 사용해야 한다.
특히 엔티티와 값 타입을 혼동해서 엔티티를 값 타입으로 만들면 안 된다.
식별자가 필요하고 지속해서 값을 추적하고 구분하고 변경해야 한다면 그것은 값 타입이 아닌 엔티티이다.
2. 임베디드 타입(복합 타입)
새로운 값 타입을 직접 정의해서 사용할 수 있는데, JPA에서는 이것을 임베디드 타입(embedded type)이라 한다.
※ 임베디드 타입은 값 타입이다.
만약 Member라는 엔티티가 city, street, zipcode라는 필드를 갖고 있다고 하자.
city, street, zipcode 등은 모두 '주소'라는 공통 개념 하에 묶일 수 있다.
실제로도 Member 엔티티가 city, street, zipcode를 각각 개별적으로 나누어서 갖는 것보다 Address라는 값 하나를 갖고 있는 것이 코드적으로도 더 명확해질 것이다.
임베디드 타입을 사용하면 위와 같은 코드를 하나의 값으로 묶을 수 있다.
[임베디드 타입 적용]
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
private String name;
@Embedded
Address address;//주소
}
@Embeddable
public class Address {
@Coulmn(name="city") //매핑할 컬럼 정의 가능
private String city
private String street;
private String zipcode;
}
● @Embedded : 값 타입을 사용하는 곳에 표시
● @Embeddable : 값 타입을 사용하는 곳에 표시
※ 둘 중 한 개의 어노테이션은 생략해도 된다.
임베디드 타입을 사용하는 데에는 다음과 같은 장점이 있다.
● 새로 정의한 값 타입들은 재사용할 수 있다.
● 코드적으로 응집도가 높다.
● 값 타입만 사용할 수 있는 메서드를 만들 수 있다.
[임베디드 타입 - 테이블 매핑]
임베디드 타입은 엔티티의 값일 뿐이다.
따라서 값이 속한 엔티티의 테이블에 그대로 매핑된다.
이 때문에 임베디드 타입을 사용하기 전과 후에 매핑되는 테이블의 모양은 같다.
■ 추가 특성
● @AttributeOverride를 사용하면 임베디드 타입을 재정의 할 수 있다.
◎ 한 엔티티에 복수의 임베디드 타입을 사용하면 컬럼명이 중복되기 때문에 하나 이상의 임베디드 타입을 재정의 해줄 필요가 있다.
ex) @AttributeOverride(name="city", column=@Column(name = "COMPANY_CITY")), ...)
● 임베디드 타입과 연관관계
◎ 임베디드 타입은 값 타입을 포함하거나 엔티티를 참조할 수 있다.
● 임베디드 타입과 null
◎ 임베디드 타입이 null이면 매핑한 컬럼 값은 모두 null이 된다.
■ 값 타입과 불변 객체
int, double과 같은 기본 타입(primitive type)은 항상 값을 복사하기 때문에 공유될 일이 없다.
또한, Iteger 같은 래퍼 클래스나 String 같은 특수한 클래스는 공유 가능한 객체이지만 변경이 불가하다.
사용자가 직접 정의하는 값 타입인 임베디드 타입은 자바의 기본 타입이 아니라 객체 타입이다.
따라서 임베디드 타입을 대입할 때는 원본의 참조 값이 직접 넘어가게 된다.
이 때문에 객체의 공유 참조가 발생할 수 있다.
이런 문제를 해결하기 위한 방법 중 하나는 객체의 값을 수정하지 못하게 막는 것이다.
예를 들어, Adress와 같은 임베디드 타입은 생성자로만 값을 초기에 결정할 수 있고 setter와 같은 수정자 메서드를 만들지 않으면 공유 참조를 해도 값을 변경하지 못하므로 부작용의 발생을 막을 수 있다.
값 타입은 되도록이면 불변 객체로 설계해야 한다.
3. 값 타입 컬렉션
값 타입을 하나 이상 저장하려면 컬렉션에 보관하고 @ElementCollection, @CollectionTable 어노테이션을 사용하면 된다.
[갑 타입 컬렉션 사용]
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
private String name;
@ElementCollection
@CollectionTable(name = "FAVORIT_FOODS",
joinColumns = @JoinColumn(name = "MEMBER_ID"))
@Column(name="FOOD_NAME")
private Set<String> favoritFoods = new HasSet<String>();
}
@ElementCollection으로 값 타입 컬렉션을 지정해 주었다.
관계형 데이터베이스의 테이블은 컬럼안에 컬렉션을 포함할 수 없기 때문에 @CollectionTable로 추가적인 테이블을 매핑해야 한다.
[값 타입 컬렉션 매핑]
값 타입 컬렉션은 값 타입이기 때문에 해당 엔티티를 영속화하면 따라서 값 타입 컬렉션도 영속화된다.
■ 값 타입 컬렉션의 제약사항
엔티티는 식별자가 있으므로 엔티티의 값을 변경해도 식별자로 데이터베이스에 저장된 원본 데이터를 쉽게 찾아서 변경할 수 있다.
반면에 값 타입은 식별자라는 개념이 없기 때문에 값을 변경해버리면 데이터베이스에 저장된 원본 데이터를 찾기가 어렵다.
이런 문제로 인해 JPA 구현체들은 값 타입 컬렉션에 변경 사항이 발생하면, 값 타입 컬렉션이 매핑된 테이블의 연관된 모든 데이터를 삭제하고, 현재 값 타입 컬렉션 객체에 있는 모든 값을 데이터베이스에 다시 저장한다.
따라서 실무에서는 값 타입 컬렉션이 매핑된 테이블에 데이터가 많다면 값 타입 컬렉션 대신에 일대다 관계를 고려해야 한다.
영속성 전이 + 고아 객체 제거 기능을 적용하면 값 타입 컬렉션처럼 사용할 수 있다.
'Spring > JPA' 카테고리의 다른 글
#10 객체지향 쿼리 언어 (0) | 2021.09.01 |
---|---|
#8 프록시와 연관관계 관리 (0) | 2021.08.29 |
#7 고급 매핑 (0) | 2021.08.28 |
#6 다양한 연관관계 매핑 (0) | 2021.08.28 |
#5 연관관계 매핑 기본 (0) | 2021.08.27 |
댓글