Qestion
- 임베디드 타입의 장점은?
- 엔티티 안에 같은 임베디드 타입이 여러개라면 무슨 문제가 발생하고 어떻게 해결하는가?
- 임베디드 타입을 사용할 때 주의점과, 어떻게 문제를 방지할 수 있는가?
- 값 타입 컬렉션의 문제점은?
- 값 타입 컬렉션의 대안은?
- 엔티티 타입과 값 타입의 주요 차이점은?
JPA 값 타입 분류
- 기본 값 타입
- 생명주기를 엔티티에 의존(회원을 삭제하면 이름, 나이 필드도 함께 삭제)
- 값 타입은 공유하면 안됨(회원 이름을 변경시 다른 회원의 이름도 함께 변경되면 안됨)
- 자바 기본 타입(int, double)
- 래퍼 클래스(Integer, Long)
- String
- 임베디드 타입
- 새로운 값 타입을 직접 정의할 수 있음
- 주로 기본 값 타입을 모아 만들기 때문에 복합 값 타입이라고도 함
- 컬레션 값 타입
임베디드 타입
- city, street, zipcode와 같은 서로 연관 있는 String들을 묶어서 Address라는 임베디드 타입을 생성
- 임베디드 타입도 값 타입의 한 종류
- 사용법
- @Embeddable: 값 타입을 정의하는 곳에 표시
- @Embedded: 값 타입을 사용하는 곳에 표시
- 기본 생성자 필수로 생성
- 장점
- 재사용성
- Address 를 만들어 Member에 사용할 수도 있고, 추후에 다른 Manager와 같은 클래스에도 사용 가능
- Address 하위에 util 함수를 만들거나, zipcode에 length를 걸거나 하는 것도 여러 곳에서 계속 재사용됨
- 높은 응집도
- 서로 연관 있는 것들 끼리 묶어 응집도가 높아짐
- 임베디드 타입을 포함한 모든 값 타입은, 값 타입을 소유한 엔티티에 생명주기를 의존함(Cascade.ALL, orphanRemoval = true)
- 테이블 맵핑
- 임베디드 타입은 사실상 테이블 입장에서는 그냥 값일 뿐
- 임베디드 타입을 사용하기 전과 후에 맵핑 테이블은 사실 똑같음
- 객체와 테이블을 아주 세밀하게 맵핑 가능
- 잘 설계한 ORM 애플리케이션은 맵핑한 테이블의 수보다 클래스의 수가 더 많음
- 임베디드 타입 아래 또 Entity가 있을 수도 있음
- 임베디드 타입의 값이 null 이면 맵핑한 컬럼 값도 모두 null
@AttributeOverride
@Entity
public class Member{
@Id
private String MEMBER_ID;
@Embedded
private Address homeAddress;
@Embedded
@AttributeOverrides({
@AttributeOverride(name="city"
column=@Column("WORK_CITY")),
@AttributeOverride(name="street"
column=@Column("WORK_STREET")),
@AttributeOverride(name="zipcode"
column=@Column("WORK_ZIPCODE"))
})
private Address workAddresss;
...
}
- 한 엔티티 내에서 같은 임베디드 타입이 있는 경우 컬럼 명이 중복되는 문제 발생
- @AtributeOverrides, @AttributeOverride를 통해 컬럼명 속성을 재정의 가능
객체 타입의 한계
- 임베디드 타입은 값 타입이지만 객체 타입임
- 객체 타입이라는 말은 참조 값을 사용한다는 것이고, 참조 값을 사용하면 값이 공유될 수 있음
- 따라서, 값을 공유하면 안되고 값을 복사해서 새로 만들어서 사용해야 함
- 혹은, 객체 타입을 수정할 수 없게 만들면 부작용을 원천 차당 가능
- 값 타입은 불변 객체로 설계하는게 원칙
- 생성자로만 값을 설정하고 수정자는 만들지 않으면 됨
- Integer, String같은 것이 자바가 제공하는 대표적인 불변 객체
값 타입 컬렉션
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ElementCollection
@CollectionTable(name = "ADDRESS", joinColumns = @JoinColumn(name = "MEMBER_ID"))
private List<Address> addres = new ArrayList<>();
...
}
- 정규화 DB는 데이터를 하나 넘게 넣을 수 없음
- 따라서, 컬렉션 같은 데이터를 넣을 수 없어 컬렉션 저장을 위한 테이블이 필요함
- @ElementCollection, @CollectionTable을 사용하면, 자동으로 테이블을 생성해줌
- 테이블이므로 디폴트로 지연 로딩 사용됨
- 이 또한 값 타입이므로 Cascade.ALL + orphanRemoval = true
값 타입 컬렉션의 제약사항
- 엔티티와 다르게 식별자 개념이 없음.
- 자동으로 속성과 부모의 Primary Key를 만들어주지만 별도의 식별자가 없음
- 따라서 값 변경에 대한 추적이 어려움
- 값 타입 컬렉션에 변경 사항이 발생하면, 부모 엔티티와 연관된 모든 데이터를 삭제하고, 기존+변경 데이터를 다시 넣는다
- 값 타입 컬렉션은 식별자가 없는 단순한 값으로만 이루어져 각 값을 추적하기 어려움
- 이러한 방법은 매우 비효율적
- 모든 컬럼을 묶어 Primary Key를 구성해야 함.
- 따라서 null 입력 X, 중복 저장 X
값 타입 컬렉션의 대안
- 실무에서는 상황에 따라 값 타입 컬렉션 대신 일대다 관계를 고려
- 일대다 엔티티를 만들고 여기에 값 타입을 사용
- Cascade + 고아 객체로 값 타입 컬렉션처럼 사용
- 별도의 Primary Key를 만들 수 있고 기존 값 타입 컬렉션의 문제점을 해결 가능
- 값 타입 컬렉션은 정말 정말 간단한 케이스에서만 가끔 사용은 가능함
정리
- 엔티티 타입 특징
- 식별자가 있음
- 생명 주기를 스스로 관리
- 공유 가능
- 값 타입의 특징
- 식별자가 없음
- 생명 주기를 엔티티에 의존
- 공유하지 않는 것이 안전(복사해서 사용)
- 불변 객체로 만드는 것이 안전
- 엔티티 타입은 공유 가능하고 값 타입은 그렇지 않은 이유
- 엔티티 타입은 DB 상에서 유일하게 식별 되므로, 여러 곳에서 공유 가능
- 값 타입은 유일하지도 않고 식별자도 없으며 값 자체를 의미하므로 공유할 필요도 없음
- 식별자가 필요하고, 지속해서 값 추적이 필요하고, 변경이 필요하다면 그것은 값 타입이 아닌 엔티티로 선언