JPA 프로그래밍 입문 - Chapter4. 밸류와 @Embeddable
by Gunju Ko
이 글은 “JPA 프로그래밍 입문 (최범균 저)” 책 내용을 정리한 글입니다.
만약 저작권 관련 문제가 있다면 “gunjuko92@gmail.com”로 메일을 보내주시면, 바로 삭제하도록 하겠습니다.
Chapter4. 밸류와 @Embeddable
1. 밸류로 의미를 더 드러내기
밸류는 다음과 같은 특징을 가진다.
- 밸류는 개념적으로 한 개의 값을 표현한다.
- 다른 밸류 객체와 구분하기 위한 식별자를 갖지 않는다.
- 자신만의 라이프사이클을 갖지 않는다. 밸류는 자신이 속한 객체가 생성될 때 함께 생성되고 삭제될 때 함께 삭제된다.
밸류 객체를 사용하는 이유는 값의 의미를 더 잘 드러내기 때문이다.
2. 밸류 클래스의 구현
밸류 클래스 구현
- 생성 시점에 모든 프로퍼티를 파라미터로 전달받는다.
- 읽기 전용 프로퍼티만 제공한다.
- 각 프로퍼티의 값을 비교하도록 equals() 메소드를 재정의한다.
- 각 프로퍼티의 값을 이용해서 해시코드를 생성하도록 hashCode() 메소드를 재정의한다.
3. @Embeddable 애노테이션을 이용한 밸류 매핑
Address 밸류 타입을 갖는 Hotel 엔티티를 테이블에 매핑하려면 다음과 같은 매핑 설정을 추가해야 한다.
- 밸류 타입인 Address 클래스에 @Embeddable 애노테이션을 적용
- Hotel 클래스는 @Embedded 애노테이션을 사용해서 밸류 타입을 매핑 설정한다.
@Embeddable 애노테이션은 대상 클래스가 다른 엔티티의 일부로 함께 저장될 수 있다는 것을 설정한다. @Embedded 애노테이션은 매핑 대상이 @Embeddable 클래스의 인스턴스라는 것을 설정한다.
3.1 null 밸류의 매핑 처리
- @Embedded 애노테이션을 적용한 밸류 객체가 널인 경우엔 매핑된 모든 컬럼에 널이 들어간다.
- @Embedded로 매핑한 컬럼의 모든 값이 널이면 조회할 때 매핑 대상도 널이 된다.
3.2 @Embeddable의 접근 타입
- @Embedded로 매핑한 대상은 해당 엔티티의 접근 타입을 따른다.
- 예를 들어 엔티티가 필드 접근 타입을 사용하면 @Embedded로 매핑한 객체를 처리할 때에도 필드 접근 타입을 사용한다.
- @Embeddable이 속한 엔티티의 접근 방식에 상관없이 접근 타입을 고정하려면 @Access 애노테이션을 사용하면 된다.
4. @Entity와 @Embeddable의 라이프사이클
- @Embedded로 매핑한 객체는 엔티티와 동일한 라이프사이클은 갖는다.
- JPA는 @Entity로 매핑한 클래스와 @Embeddable로 매핑한 클래스를 서로 다른 테이블에 저장하는 방법을 제공하는데, 이 경우에도 @Embeddable의 라이프사이클은 엔티티를 따른다.
5. @AttributeOverrides를 이용한 매핑 설정 재정의
- @AttributeOverrides 애노테이션과 @AttributeOverride 애노테이션을 사용하면 @Embedded로 매핑한 값 타입의 매핑 설정을 재정의할 수 있다.
@Entity
public class Sight {
@Id
private Long id;
private String name;
@Embedded
private Address korAddress;
@Embedded
@AttributeOverrides({
@AttributeOverride(name = "zipcode", column = @Column(name = "eng_zipcode")),
@AttributeOverride(name = "address1", column = @Column(name = "eng_addr1")),
@AttributeOverride(name = "address2", column = @Column(name = "eng_addr2"))
})
private Address engAddress;
}
@AttributeOverride 애노테이션은 개별 매핑 대상에 대한 설정을 재정의한다. name 속성으로 재정의할 대상을 설정하고 column 속성으로 변경할 매핑 컬럼을 지정한다. @AttributeOverrides 애노테이션은 여러 개의 @AttributeOverride를 설정할 때 사용한다.
6. @Embeddable 중첩
@Embeddable로 지정한 클래스에 또 다른 @Embeddable 타입을 중첩해서 매핑할 수 있다.
7. 다른 테이블에 밸류 저장하기
- 밸류 객체를 반드시 같은 테이블에 저장해야 하는 것은 아니다.
- @Entity 클래스에 @SecondaryTable로 밸류를 저장할 테이블 지정
- @Entity 클래스에 @AttributeOverride로 컬럼의 테이블 이름을 재정의
@Entity
@SecondaryTable(
name = "sight_detail",
pkJoinColumns = @PrimaryKeyJoinColumn(name = "sight_id", referencedColumnName = "id")
)
public class Sight {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@Embedded
@AttributeOverrides({
@AttributeOverride(name = "hoursOfOperation", column = @Column(name = "hours_op", table = "sight_detail")),
@AttributeOverride(name = "holiday", column = @Column(table = "sight_detail")),
@AttributeOverride(name = "facilities", column = @Column(table = "sight_detail"))
})
private SightDetail detail;
}
- @SecondaryTable은 데이터의 일부를 다른 테이블로 매핑할 때 사용
- name : 테이블의 이름을 지정
- pkJoinColumns : @SecondaryTable로 지정한 테이블에서 @Entity와 매핑되는 테이블의 주요키를 참조할 때 사용할 컬럼을 설정한다. @PrimaryKeyJoinColumn의 name 속성은 @SecondaryTable의 컬럼 이름을 지정하고, referencedColumnName 속성은 @Entity의 @Id와 매핑되는 컬럼 이름을 지정한다.
- @AttributeOverride를 이용해서 어떤 값을 sight_detail 테이블에서 읽어올지 설정한다.
- detail 필드가 널인 경우엔 sight 테이블에 대해서만 insert 쿼리를 실행한다.
- @EntityManager.find()로 엔티티를 조회하면 @SecondaryTable로 매핑한 테이블을 레프트 조인으로 조회한다.
- Sight를 조회할 때 SightDetail에 매핑되는 sight_detail 테이블에 데이터가 존재하지 않으면 해당 매핑 대상의 값은 널이 된다.
아래와 같이 SightDetail과 매핑할 테이블 설정을 SightDetail에서 직접 설정해도 된다.
@Embeddable
public class SightDetail {
@Column(name = "hours_op", table = "sight_detail")
private String hoursOfOperation;
@Column(name = "holidays", table = "sight_detail")
private String holidays;
@Column(name = "facilities", table = "sight_detail")
private String facilities;
}
7.1 다른 테이블에 저장한 @Embeddable 객체 수정과 쿼리
sight_detail 테이블에 데이터 존재 여부 | Sight의 detail 필드 변경 | sight_detail 테이블에 대한 실행 쿼리 |
---|---|---|
존재함 | 새 객체 할당 | update |
존재함 | null 객체할당 | delete |
존재하지 않음 | 새 객체 할당 | insert |
존재함 | 기존 SightDetail 객체의 set 메소드를 이용 변경 | update |
존재하지 않음 | null | 쿼리 실행하지 않음 |
8. @Embeddable의 복합키
- 테이블의 주요키가 두 개 이상의 컬럼으로 구성된 복합키이고 이 복합키를 엔티티의 식별자에 매핑해야 할 경우, @Embeddable 타입을 복합키에 매핑할 식별자 타입으로 사용할 수 있다.
- 일반 밸류 클래스와 비교해서 복합키로 사용할 밸류 클래스는 다음의 차이가 있다.
- 값 비교를 위한 equals() 메소드와 hashCode() 메소드를 알맞게 구현해야 한다.
- Serializable 인터페이스를 상속해야 한다.
@Embeddable
public class MonChargeId implements Serializable {
@Column(name = "hotel_id")
private String hotelId;
@Column(name = "year_mon")
private String yearMon;
public MonChargeId() {}
public MonChargeId(String hotelId, String yearMon) {
this.hotelId = hotelId;
this.yearMon = yearMon
}
// equals, hashCode 메소드 구현 생략
}
@Entity
@Table
public class MonthCharge {
@Id
private String MonthChargeId id;
// 생략
}