•
이 장에선 다음과 같은 고급 매핑 요소들을 다룬다.
◦
상속 관계 매핑: 객체의 상송 관계를 데이터베이스에 어떻게 매핑하는 지 알아본다.
◦
@MappedSuperclass: 등록일, 수정이 같이 여러 엔티티에서 공통으로 사용하는 매핑 정보를 상속받고 싶으면 이 기능을 사용한다.
◦
복합 키와 식별 관계 매핑: 데이터베이스의 식별자가 하나 이상일 때 매핑하는 방법을 다룬다. 그리고 데이터베이스 설계에서 이야기하는 식별 관계와 비식별 관계에 대해서도 배운다.
◦
조인 테이블: 테이블은 외래 키 하나로 연관관계를 맺을 수 있지만 연관관계를 관리하는 연결 테이블을 두는 방법도 있다. 이 연결 테이블을 매핑하는 방법을 다룬다.
◦
엔티티 하나에 여러 테이블 매핑하기: 보통 엔티티 하나에 테이블 하나를 매핑하지만 엔티티 하나에 여러 테이블을 매핑하는 방법도 있다.
1. 상속 관계 매핑
•
관계형 데이터베이스에는 상속과 가장 유사한 개념으로 슈퍼타입, 서브타입 모델링 기법을 가지고 있다. ORM의 상속 관계 매핑은 객체의 상속 구조와 데이터베이스의 슈퍼타입, 서브타입 관계를 매핑하는 것이다.
•
해당 논리 모델을 실제 물리 모델인 테이블로 구현할 때는 3가지 방법을 선택할 수 있다.
◦
각각의 테이블로 변환: 각각을 모두 테이블로 만들고 조회할 때 조인을 사용한다. JPA에서는 조인 전략이라 한다.
◦
통합 테이블로 변환: 하나만 사용해서 통합한다. JPA에선 단일 테이블 전략이라 한다.
◦
서브타입 테이블로 변환: 서브 타입마다 하나의 테이블을 만든다. JPA에서는 구현 클래스마다 테이블 전략이라 한다.
1. 조인 전략
•
조인 전략은 엔티티 각각을 모두 테이블로 만들고 자식 테이블이 부모 테이블의 기본 키를 받아 기본 키 + 외래 키로 식별관계를 사용하는 전략이다. 따라서 조회 시 조인을 자주 사용한다.
•
이 전략을 사용할 때 타입을 구분하는 칼럼을 추가해야 한다. 객체는 타입으로 구분할 수 있지만 테이블은 타입의 개념이 없기 때문이다.
•
장점
◦
테이블이 정규화된다.
◦
외래 키 참조 무결성 제약조건을 활용할 수 있다.
◦
저장공간을 효율적으로 사용한다.
•
단점
◦
조회 시 조인이 많이 사용되어 성능이 저하될 수 있다.
◦
조회 쿼리가 복잡해진다.
◦
데이터 등록 시, 삽입 SQL이 여러번 실행된다.
•
관련 어노테이션
◦
@PrimaryKeyJoinColumn, @DiscriminatorColumn, @DiscrimanatorValue
2. 단일 테이블 전략
•
단일 테이블 전략은 테이블을 하나만 사용한다. 그리고 구분 컬럼으로 어떤 자식 데이터가 저장되어 있는지 구분한다.
•
InheritanceType.SINGLE_TABLE로 지정하면 사용하며 이 전략을 사용할 때, 자식 엔티티와 매핑된 컬럼은 모두 null을 허용해야 한다.
•
장점
◦
조인이 필요 없으므로 일반적으로 조회 성능이 빠르다.
◦
조회 쿼리가 단순하다.
•
단점
◦
자식 엔티티가 매핑한 컬럼은 모두 null을 허용해야 한다.
◦
단일 테이블에 모든 것을 저장하므로 테이블이 커질 수 있다. 그러므로 상황에 따라서는 조회 성능이 오히려 느려질 수 있다.
•
특징
◦
구분 컬럼을 꼭 사용해야 한다. 따라서 @DiscriminaotrColumn을 꼭 설정해야 한다.
◦
@DiscriminiatorValue를 지정하지 않으면 기본으로 엔티티 이름을 사용한다.
3. 구현 클래스마다 테이블 전략
•
자식 엔티티마다 테이블을 만들어 사용하는 전략이다.
•
InheritanceType.TABLE_PER_CLASS로 지정하면 구현 클래스마다 테이블 전략을 사용한다. 이 전략은 자식 엔티티마다 테이블을 생성하며 일반적으로 추천되진 않는 전략이다.
•
장점
◦
서브 타입을 구분해서 처리할 때 효과적이다.
◦
not null 제약조건을 사용할 수 있다.
•
단점
◦
여러 자식 테이블을 함께 조회할 때 성능이 느리다.
◦
자식 테이블을 통합해서 질의하는 것이 어렵다.
•
이 전략은 DBA와 ORM 전문가 둘 다 추천하지 않는 전략이다. 조인이나 단일 테이블 전략을 고려하자.
2. @MappedSuperclass
•
테이블과 매핑하지 않고 부모 클래스를 상속받는 자식 클래스에게 매핑 정보만 제공하고 싶을때 사용한다.
•
엔티티가 아니므로 em.find()나 JPQL에서 사용할 수 없다.
•
이 클래스를 직접 생성해서 사용할 일은 거의 없으므로 추상 클래스로 구현하는 것을 권장한다.
•
이를 사용하면 등록일자, 수정일자, 등록자, 수정자와 같은 공통 속성들을 효과적으로 관리할 수 있다.
3. 복합 키와 식별 관계 매핑
1. 식별 관계 vs 비식별 관계
•
식별 관계
◦
부모 테이블의 기본 키를 받아 자식 테이블의 기본 + 외래 키로 사용하는 관계다.
•
비식별 관계
◦
부모 테이블의 기본 키를 받아 자식 테이블의 외래 키로만 사용하는 관계다.
◦
비식별 관계는 외래 키에 NULL을 허용하는지에 따라 필수적 비식별 관계와 선택적 비식별 관계로 나뉜다.
▪
필수적 비식별 관계: 외래 키에 NULL을 허용하지 않는다. 연관관계를 필수적으로 맺어야 한다.
▪
선택적 비식별 관계: 외래 키에 NULL을 허용한다. 연관관계를 맺을지 말지 선택할 수 있다.
•
데이터베이스 테이블을 설계할 때 식별 관계나 비식별 관계 중 하나를 선택해야 한다. 최근은 비식별 관계를 주로 사용하고 꼭 필요한 곳에만 식별 관계를 사용하는 추세다. JPA는 둘 모두 지원한다.
2. 복합 키: 비식별 관계 매핑
•
둘 이상의 컬럼으로 구성된 복합 기본 키는 별도의 식별자 클래스를 만들어야 한다.
•
JPA는 복합 키를 지원하기 위해 @IdClass와 @EmbeddedId 2가지 방법을 지원하는데, 후자가 좀더 객체지향적인 방법이다.
•
@IdClass
◦
복합 키 정보를 가지고 있는 클래스를 선언 후, 해당 복합 키를 가진 클래스에 @IdClass(클래스명.class) 어노테이션을 달아주면 된다.
◦
이때 식별자 클래스는 다음 조건을 만족해야 한다.
▪
식별자 클래스의 속성명과 엔티티에서 사용하는 식별자의 속성명이 같아야 한다.
▪
Serializable 인터페이스를 구현해야 한다.
▪
equals, hashCode를 구현해야 한다.
▪
기본 생성자가 있어야 한다.
▪
식별자 클래스는 public이어야 한다.
•
@EmbeddedId
◦
식별자 클래스에 기본 키를 직접 매핑하고 엔티티에 @EmbeddedId 어노테이션을 적어주면 된다.
◦
식별자 클래스는 다음 조건을 만족해야 한다.
▪
@Embeddable 어노테이션을 붙여주어야 한다.
▪
Serializable 인터페이스를 구현해야 한다.
▪
equals, hashCode를 구현해야 한다.
▪
기본 생성자가 있어야 한다.
▪
식별자 클래스는 public이어야 한다.
•
복합 키와 equals(), hashCode()
◦
영속성 컨텍스트는 엔티티의 식별자를 키로 사용해서 엔티티를 관리하며 식별자를 비교할 때 equals()와 hashCode()를 사용한다. 따라서 적절히 재정의해주어야 한다.
◦
식별자 클래스는 보통 이를 구현할 때, 모든 필드를 사용해서 동등성을 비교한다.
•
@IdClass vs @EmbeddedId
◦
@EmbeddedId가 더 객체지향적이고 중복도 없어서 좋아보이나 특정 상황에 JPQL이 조금 더 길어질 수 있다.
3. 복합 키: 식별 관계 매핑
•
식별 관계에서 자식 테이블은 부모 테이블의 기본 키를 포함해 복합 키를 구성해야 한다.
•
식별 관계는 기본 키와 외래 키를 같이 매핑해야 하므로 식별자 매핑과 연관관계 매핑을 같이 사용하면 된다.
•
@EmbeddedId로 식별 관계를 구성할 때는 @MapsId를 사용해야 한다.
6. 식별, 비식별 관계의 장단점
•
데이터베이스 설계 관점에서 식별보단 비식별을 더 선호하는 이유는 다음과 같다.
◦
식별 관계는 부모 테이블의 기본 키가 자식 테이블로 전파되면서 자식 테이블의 기본 키 컬럼이 점점 늘어난다.
◦
식별 관계는 2개 이상의 컬럼을 합해서 복합 기본 키를 만들어야 하는 경우가 잦다.
◦
식별 관계를 사용할 때 기본 키로 비즈니스 의미가 있는 자연 키 컬럼을 조합하는 경우가 많은데, 비식별 관계의 기본 키는 비즈니스와 전혀 관계없는 대리 키를 주로 사용한다.
◦
식별 관계는 부모 테이블의 기본 키를 자식 테이블의 기본 키로 사용하므로 비식별 관계보다 테이블 구조가 유연하지 못하다.
•
객체 관계 매핑의 관점에서 식별보다 비식별을 더 선호하는 이유는 다음과 같다.
◦
일대일 관계를 제외하고 식별 관계는 2개 이상의 컬럼을 묶는 복합 기본 키를 사용한다. JPA에서 복합 키는 별도의 복합 키 클래스를 만들어서 사용해야하므로 컬럼이 하나인 기본 키를 매핑하는 것보다 복잡하다.
◦
비식별 관계의 기본 키는 주로 대리 키를 사용하는데 JPA는 @GenerateValue처럼 대리 키를 생성하기 위한 방법을 제공한다.
•
식별 관계가 가지는 장점으로는 기본 키 인덱스를 활용하기 좋으며 상위 테이블의 기본 키 컬럼을 자식, 손자 테이블들이 가지고 있으므로 특정 상황에 조인 없이 하위 테이블만으로도 검색이 가능하다.
•
ORM 프로젝트 시 되도록이면 비식별 관계를 사용하고 기본 키는 Long 타입의 대리 키를 사용하는 것을 추천한다.
◦
선택적 비식별보단 필수적 비식별이 더 나은데, 선택적인 비식별 관계는 NULL을 허용하므로 외부 조인을 사용해야 한다.
4. 조인 테이블
•
데이터베이스 테이블의 연관관계를 설계하는 방법은 크게 2가지다.
◦
조인 컬럼 사용
▪
조인 컬럼이라 부르는 외래 키 컬럼을 사용해서 관리한다.
◦
조인 테이블 사용
▪
조인 테이블을 추가하고 여기서 두 테이블의 외래 키를 이용해 연관관계를 관리한다.
▪
연결 테이블, 링크 테이블로도 부른다.
◦
조인 컬럼은 @JoinColumn, 조인 테이블은 @JoinTable로 매핑한다.
1. 일대일 조인 테이블
•
일대일 관계를 만들려면 조인 테이블의 외래 키 컬럼 각각에 총 2개의 유니크 제약조건을 걸어야 한다.
•
참조 객체 속성에 @JoinTable(name = “매핑할 조인 테이블명”, joinColumns = @JoinColumn(name = “현재 엔티티를 참조하는 외래 키”), inverseJoinColumns = @JoinColumn(name = ”CHILD_ID”))를 매핑해주면 된다.
2. 일대다 조인 테이블
•
일대다 관계를 만들려면 조인 테이블의 컬럼 중 다인 컬럼에 유니크 제약조건을 걸어야 한다.
3. 다대다 조인 테이블
•
다대다 관계를 만들려면 조인 테이블의 두 컬럼을 합해서 하나의 복합 유니크 제약 조건을 걸어야 한다.
5. 엔티티 하나에 여러 테이블 매핑
•
@SecondaryTable을 사용하면 한 엔티티에 여러 테이블을 매핑할 수 있다.
•
각각 엔티티를 만들어 일대일 매핑하는 것을 권장한다. 이 방법은 항상 두 테이블을 조회하므로 최적화가 어렵다.