값 타입
int, Integer, String처럼 단순히 값으로 사용하는 자바 기본 타입이나 객체이며 식별자가 없고 값만 있으며 변경 시 추적 불가
예) 숫자 100을 200으로 변경하면 완전히 다른 값으로 대체됨
불변 객체
@Entity로 정의하는 객체로 데이터가 변해도 식별자로 지속해서 추적 가능
예) 회원 엔티티의 키나 나이 값을 변경해도 식별자로 인식 가능
예) String name, int age 처럼 자바 기본 타입(int, double 등), 래퍼 클래스(Integer, Long 등), String
생명주기를 엔티티에 의존함(예를 들어 회원을 삭제하면 이름, 나이 필드도 함께 삭제)
* 값 타입은 공유하면 안됨 | 자바의 기본 타입(int, double 등)은 절대 공유하면 안되고 항상 값을 복사해서 사용해야 함!
새로운 값 타입을 직접 정의할 수 있다. JPA는 임베디드 타입이라 하며 주로 기본 값 타입을 모아서 복합 값 타입이라고도 한다.
임베디드 타입의 값이 null이면 매핑한 컬럼 값은 모두 null
@Embeddable: 값 타입을 정의하는 곳에 표시 - Period, Address 객체
@Embeddable
public class Address {
String street;
String city;
@Embedded
Zipcode zipcode;
}
@Embedded: 값 타입을 사용하는 곳에 표시 - Member 객체
@Entity
public class Member {
@Embedded
Period workPeriod;
@Embedded
Address homeAddress;
}
장점으로는 재사용성이 높고 해당 값 타입만 사용하는 의미있는 메서드를 만들 수 있다는 점이 있다.
임베디드 타입을 포함한 모든 값 타입은 값 타입을 소유한 엔티티의 생명주기에 의존한다.
임베디드 타입은 엔티티의 값일 뿐, 임베디드 타입을 사용하기 전과 후에 매핑하는 테이블은 같음
객체와 테이블을 아주 세밀하게 매핑하는 것 가능
잘 설계한 ORM 애플리케이션의 경우 매핑한 테이블 수보다 클래스의 수가 더 많음
한 엔티티에서 같은 값 타입을 사용하면 컬럼명이 중복되는데 @AttributeOverride / @AttributeOverrides 어노테이션을 사용하여 같은 컬럼명의 속성을 재정의한다.
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
private String name;
@Embedded
Address homeAddress;
@Embedded
@AttributeOverrides({
@AttributeOverride(name="city", column=@Column(name="COMPANY_CITY")),
@AttributeOverride(name="street", column=@Column(name="COMPANY_STREET")),
@AttributeOverride(name="zipcode", column=@Column(name="COMPANY_ZIPCODE"))
})
Address companyAddress;
}
값 타입을 컬렉션에 담아 사용하며 컬렉션에 기본 값 혹은 임베디드 값 타입을 넣은 형태로 JPA에서 정의해서 사용해야 한다.
값 타입은 복잡한 객체 관계를 단순화하고자 만든 개념이다. 따라서 값 타입은 단순하고 안전하게 다룰 수 있어야 한다.
임베디드 타입과 같은 값 타입을 여러 엔티티에서 공유하면 위험하다.
Address address = new Address("street", "city", zipcode);
member1.setHomeAddress(address);
member2.setHomeAddress(address);
member2.getHomeAddress().setCity("newCity");
member1과 member2가 같은 address를 공유함으로 update가 member1과 member2 두번 이뤄짐
값 타입의 실제 인스턴스인 값을 공유하는 것은 매우 위험함으로 값을 복사해서 사용
Address address2 = new Address(address);
member2.setHomeAddress(address2);
member2.getHomeAddress().setCity("newCity");
서로 다른 값타입을 갖게 됨으로 member2의 address만 변경됨
항상 값을 복사해서 사용하면 공유 참조로 인해 발생하는 부작용은 피할 수 있으나 문제는 임베디드 타입처럼 직접 정의한 값 타입은 자바의 기본 타입이 아니라 객체 타입이다. 자바 기본 타입에 값을 대입하면 값을 복사하지만 객체 타입은 참조 값을 직접 대입하는 것을 막을 방법이 없어 객체의 공유 참조는 피할 수 없다.
기본타입
int a = 10;
int b = a;
b = 4;
// a=10, b=4
객체 타입
Address a = new Address("old");
Address b = a;
b.setCity("new");
// a.city=new, b.city=new
객체 타입을 수정할 수 없게 만들어 부작용을 원천 차단
값 타입은 불면 객체로 설계하여 생성 시점 이후 절대 값을 변경할 수 없도록 해야함 -> 생성자로만 값을 설정하고 수정자(setter)를 만들지 않는다.
값 타입 비교 시, 인스턴스가 달라도 그 안의 값이 같으면 같은 것으로 봐야함
- 동일성(identity) 비교 : 인스턴스의 참조 값 비교
Address a = new Address("a");
Address b = new Address("a");
System.out.println("a==b"); // false
- 동등성(equivalence) 비교 : 인스턴스의 값 비교하며 적절하게 재정의하여 사용
a.equals(b) // true
값 타입 컬렉션이란 값 타입을 하나 이상 저장할 때 사용하며 @ElementCollection, @CollectionTable 어노테이션 등이 있다.
데이터베이스는 컬렉션을 같은 테이블에 저장할 수 없어 별도의 테이블이 필요하다.
값 타입 컬렉션은 영속성 전이(cascade)와 고아 객체 제거 기능을 필수로 가진다고 볼 수 있다.
* 제약사항
값 타입은 엔티티와 다르게 식별자 개념이 없어 변경하면 추적이 어렵다.
값 타입 컬렉션에서 변경 사항이 발생하면 주인 엔티티와 연관된 모든 데이터를 삭제하고 값 타입에 컬렉션에 있는 현재 값을 다시 저장해야 한다.
값 타입 컬렉션을 매핑하는 테이블은 모든 컬럼을 묶어서 기본 키를 구성해야 한다.
* 대안
상황에 따라 값 타입 컬렉션 대신 일대다 관계를 고려한다.
일대다 관계를 위한 엔티티를 만들고 여기서 영속성 전이와 고아 객체 제거를 사용하여 값 타입 컬렉션처럼 사용한다.
* 정리
값 타입은 정말 값 타입이라 판단될 때만 사용한다.
엔티티와 값 타입을 혼동해서 엔티티를 값 타입으로 만들면 안된다.
식별자가 필요하고 지속해서 값을 추적, 변경해야 한다면 그것은 값 타입이 아닌 엔티티!
엔티티 타입 | 값 타입 |
식별자 O | 식별자 X |
생명 주기 관리 | 생명 주기를 엔티티에 의존 |
공유 | 공유하지 않는 것이 안전(복사해서 사용) |
불변 객체로 만드는 것이 안전 |
[SPRING] JPA 활용1 | 웹 애플리케이션 개발 (0) | 2021.04.16 |
---|---|
[SPRING] JPA 프로그래밍 기본 | 객체지향 쿼리 언어 (0) | 2021.04.08 |
[SPRING] JPA 프로그래밍 기본 | 프록시와 지연로딩 (0) | 2021.03.11 |
[SPRING] JPA 프로그래밍 기본 | 상속 (0) | 2021.03.09 |
[SPRING] JPA 프로그래밍 기본 | 연관관계 (0) | 2021.03.09 |
댓글 영역