
지난 번에는 일대다, 다대일, 다대다 매핑을 직접 알아보면서
외래키를 어디에 두어야 하는가에 대해 알아보는 과정을 거쳤습니다.
이번에는 상속 관계 매핑, @MappedSuperclass, 복합키와 식별 관계 매핑, 조인테이블, 엔티티 하나에 여러 테이블 매핑하기를
차례대로 알아보겠습니다.
7.1 상속 관계 매핑
앞선 장에서 계속 이야기했던 것처럼 관계형 데이터베이스는 상속이라는 개념이 존재하지 않습니다.
그나마 유사한 것은 Super-Type Sub-Type Relationship 모델링 기법입니다.
이를 이용해서 구현할 수도 있습니다.
✅ 각각의 테이블로 변환
모두 테이블로 만들고 조회 시 조인 사용 (조인 전략)
✅ 통합 테이블로 변환
테이블을 하나만 사용하여 통합 ( 단일 테이블 전략 )
✅ 서브타입 테이블로 변환
서브 타입마다 하나의 테이블을 만듭니다.
( 테이블 전략 )
이제 차근차근 알아보도록 하겠습니다.
7.1.1 조인 전략

엔티티를 모두 테이블로 만들어서 자식테이블은 부모 테이블의 기본키를 받아 기본키와 외래키로 사용합니다.
그러므로 조회 시에는 조인이 필요합니다.
📍주의!
객체는 타입이 있지만 테이블에는 타입이 없으므로 타입을 구분하는 용도의 컬럼을 추가해야 합니다.
@Entity
@Inheritance(strategy = Inheritancetype.JOINED)
@DiscriminatorColumn(name="DTYPE")
public abstract class Item {
@Id @GeneratedValue
@Column(name = "ITEM_ID")
private Long id;
private String name;
private int price;
...
}
@Entity
@DiscriminatorValue("A")
public class Album extends Item {
private String artist;
...
}
@Entity
@DiscriminatorValue("M")
public class Movie extends Item{
private String director;
private String actor;
...
}
✅ @Interitance(strategy = InheritanceType.JOINED)
상속 매핑은 부모 클래스에 @Interitance를 사용해야 합니다.
매핑 전략으로는 조인 전략을 사용해주었습니다.
✅ DiscriminatorColumn(name = "DTYPE")
부모 클래스에 구분 컬럼을 지정합니다
이 컬럼을 이용해서 자식 테이블을 구분합니다.
✅ @DiscrimintorValue("M")
엔티티 저장 시에 부모 클래스에서 구분할 수 있는 구분 컬럼에 입력할 값을 지정합니다.
여기서는 영화 => M으로 지정했습니다.
기본값으로 자식 테이블이 부모 테이블의 ID 칼럼명을 그대로 사용하나
바꾸고 싶은 경우에는 @PrimaryKeyJoinColumn(name = "정의하고 싶은 거") 으로 지정해주면 됩니다.
조인 전략에 대해 정리해보면 다음과 같습니다.
👍 장점
- 테이블이 정규화됨
- 외래 키 참조 무결성 제약조건을 활용 가능
- 저장공간을 효율적으로 사용
👎단점
- 조회할 때 조인이 많이 사용되므로 성능 저하 우려
- 조회 쿼리 복잡
- 데이터 등록 시 INSERT SQL 두 번 실행 됨
✅ 특징
- JPA 표준 명세는 구분 컬럼을 사용하지만 하이버네이트 등의 구현체는 구분 컬럼 없이도 작동
7.1.2 단일 테이블 전략
테이블을 하나만 사용하게 됩니다.
구분컬럼으로는 어떤 자식 데이터가 저장되었는지 구분하고
앞서 살펴본 조인 전략과 다르게 조회 시 조인을 사용하지 않아 가장 빠릅니다.

📍 주의점
: 자식 엔티티가 매핑한 컬럼은 모두 null을 허용해야 함
👍장점
- 조인이 필요 없어 일반적으로 조회 성능 빠름
- 조회 쿼리가 단순
👎단점
- 자식 엔티티가 매핑한 컬럼은 모두 null 허용
- 단일 테이블에 모든 것을 저장하므로 테이블이 커질 수 있고 너무 커지므로 조회 성능이 오히려 느려질 수도...
✅ 특징
- 구분 컬럼 꼭 사용
- @DiscriminatorValue를 지정하지 않으면 기본으로 엔티티 이름 사용
7.1.3 구현 클래스마다 테이블 전략

자식 엔티티마다 테이블을 만드는 형태입니다.
InteritanceType.TABLE_PER_CLASS를 이용하게 됩니다.
( 추천하지 않는 전략 )
👍장점
- 서브 타입을 구분해서 처리할 때 효과적
- not null 제약조건을 사용할 수 있음
👎단점
- 여러 자식 테이블을 함께 조회할 때 성능이 느림
- 자식 테이블을 통합해서 쿼리하기 어려움
✅ 특징
- 구분 컬럼사용 X
7.2 @MapperSuperclass
위의 3가지 전략은 부모와 자식을 모두 DB 테이블과 매핑했으나
부모 클래스를 테이블과 매핑하고 싶지 않고 매핑 정보만 제공하고 싶을 때는 @MapperSupeclass를 사용하게 됩니다.
실제 테이블과 매핑되지 않아서 추상클래스와 비슷한 느낌이라고 볼 수 있습니다...!

회원과 판매자는 관련이 없는 테이블이지만 공통되는 속성이 있어서
이를 부모 클래스로 모으고 객체 속성 관계로 만들었습니다.
@MappedSuperclass
public abstract class BaseEntity{
@Id @GeneratedValue
private Long id;
private String name;
...
}
@Entity
public class Member extends BaseEntity{
// ID 상속
// NAME 상속
private String email;
...
}
@Entity
public class Seller extends BaseEntity{
// ID 상속
// NAME 상속
private String shopName;
...
}
BaseEntity에서 자주 사용하는 공통 매핑 정보를 정의했고
이를 상속을 통해 자식엔티티가 사용하게 되었습니다.
BaseEntity 자체는 하나의 엔티티로 생성될 필요가 없기에
테이블과의 매핑이 전혀 필요 없어서 @MapperSuperclass를 사용했습니다.
자식에서 부모로부터 물려 받은 매핑 정보를 재정의하기 위해서
@AttributeOverrides, @AttributeOverride를 사용할 수 있고
연관관계를 재정의하고 싶다면
@AssociationOverrides, @AssociationOverride를 사용하면 됩니다.
✅ @MappedSuperclass의 특징
- 테이블과 매핑되지 않고 자식 클래스에 엔티티의 매핑 정보를 상속하기 위해 사용
- @MappedSuperclass로 지정한 클래스는 엔티티가 아니므로 em.find()나 JPQL 사용 불가
- 추상 클래스로 만드는 것 권장
매퍼슈퍼클래스는 생성일자, 수정일자 등의 공통속성을 관리하기 위해서 자주 사용됩니다.
7.3 복합 키와 식별 관계 매핑
7.3.1 식별 관계 vs 비식별 관계
✅ 식별관계
부모 테이블의 기본 키를 내려받아서 자식 테이블의 기본 키 + 외래키로 사용하는 관계
✅ 비식별 관계
부모 테이블의 기본 키를 받아서 자식 테이블의 외래키로만 사용
- 필수적 비식별 관계 : 외래키에 null 허용 X, 연관관계 필수
- 선택적 비식별 관계 : 외래 키에 NULL 허용, 연관관계 맺을지 말지 선택 가능
비식별 관계를 주로 사용하고 필요한 경우에 식별관계 사용!
7.3.2 복합 키: 비식별 관계 매핑
복합 기본키를 사용할 때 단순히 @Id를 붙이기 되면
매핑 오류가 발생하므로
별도의 식별자 클래스를 생성하는 과정이 필요하다.
JPA에서는 영속성 컨텍스트에 엔티티 보관 시 식별자를 키로 사용하고 이때 구분을 위해
equals와 hashCode를 사용해서 동등성 비교를 하면 됩니다.
식별 자 필드가 하나일 경우에는 보통 자바의 기본 타입을 사용해서 괜찮지만
복합키 사용 시에는 별도의 식별자 클래스에 equals와 hashCode 구현을 해야 합니다!
이를 위해서 @IdClass와 @EmbeddedId를 사용할 수 있습니다.
✅IdClass

테이블을 살펴보면 PARENT 테이블에서 복합키를 사용했고
이를 CHILD에서 상속시켰습니다.
이 경우에 복합키 매핑을 위한 식별자 클래스 만드는 과정을 살펴보겠습니다.
@Entity
@IdClass(ParentId.class)
public class Parent {
@Id
@Column(name = "PARENT_ID1")
private String id1;
@Id
@Column(name = "PARENT_ID1")
private String id2;
private String name;
...
}
public class ParentId implements Serializable{
private String id1;
private String id2;
public ParentId(){
}
public ParentId(String id1, String id2){
this.id1 = id1;
this.id2 = id2;
}
@Override
public boolean equals(Object o){...}
@Override
public int hashCode(){...}
}
@IdClass 사용 시 식별자 클래스는
- 식별자 클래스의 속성명과 엔티티에서 사용하는 식별자의 속성명이 같아야 함
- Serializable 인터페이스를 구현해야 함
- equals, hashCode 구현해야 함
- 기본 생성자 있어야 함
- 식별자 클래스는 public이어야 함
복합 키를 사용하는 부모 엔티티를 저장할 때는
영속성 컨테스트에 엔티티를 등록하기 직전에 복합 키 값을 사용해 식별자 클래스를 생성하고
이를 영속성 컨텍스트의 키로 사용하게 됩니다.
조회 시에는 식별자 클래스를 생성한 후 해당 클래스로 엔티티를 조회할 수 있습니다.
부모키를 외래키로 물려받은 자식 클래스의 경우에는
외래키 매핑 시 여러 컬럼 매핑이 필요하므로 @JoinColumns을 사용해야 하고
각각의 외래 키 컬럼을@JoinColumn으로 매핑하게 됩니다.
✅EmbeddedId
객체지향적인 방법!
@Entity
public class Parent{
@EmbeddedId
private ParentId id;
private String name;
...
}
식별자 클래스를 사용하지 않고 @EmbeddedId를 적어주면 됩니다.
@Embeddable
public class ParentId implements Serializable {
@Column(name = "PARENT_ID1")
private String id1;
@Column(name = "PARENT_ID2")
private String id2;
// equals, hashcode 구현
}
@EmbeddedId는 식별자 클래스에 기본 키를 직접 매핑합니다.
식별자 클래스의 조건
- @Embeddable 붙여주어야 함
- Serializable 구현해야 함
- equals, hashcode 구현해야 함
- 기본 생성자 필요
- 식별자 클래스 public
엔티티를 저장할 때와 조회 할 때 모두 직접 식별자 클래스를 생성하여 사용합니다.
✅복합 키와 equals(), hashCode()
기본 equals()는 인스턴스 참조 값 비교인 동일성 비교를 하기 때문에
식별자 객체의 동등석이 지켜지지 않으면 제대로 엔티티를 찾을 수 없습니다.
그렇기 때문에 복합키는 equals(), hashCode()를 필수로 구현해야 합니다.
✅ @IdClass vs @EmbeddedId
일관성있게 사용하면 된다!
* 복합키에서는 @GenerateValue 사용 불가
7.3.3 복합 키 : 식별 관계 매핑

자식뿐만 아니라 손자까지 기본키 + 외래키로 사용하는 식별 관계입니다.
@IdClass와 @EmbeddedId 둘 다 사용 가능합니다.
✅ IdClass와 식별 관계
식별 관계는 기본 키와 외래 키를 같이 매핑해야 하므로
식별자 매핑인 @Id와 연관관계 매핑인 @ManyToOne을 같이 사용해야 합니다.
✅ EmbeddedID와 식별 관계
식별 관계 구성 시에 @MapsId를 사용해야 합니다.
이 뜻은 외래키와 매핑한 연관관계를 기본 키에도 매핑하겠다는 것입니다.
ex : @MapsId("parentId")
속성값으로는 식별자 클래스의 기본 키 필드를 지정해줍니다.
7.3.4 비식별 관계로 구현

이전과 다르게 기본키가 아니라 외래키로만 부모의 아이디를 사용하는 비식별 관계입니다.
@Entity
public class Child{
@Id @GeneratedValaue
@Column(name = "CHILD_ID")
private Long id;
private String name;
@ManyToOne
@JoinColumn(name = "PARENT_ID")
private Parent parent;
...
}
자식만 살펴보면 식별관계로 만든 코드보다는 확실히 단순함을 느낄 수 있습니다.
7.3.5 일대일 식별관계

일대일 식별관계에서는 자식 테이블에서 부모 테이블의 기본키값을 자신의 기본키값으로 사용합니다.
그러므로 자식테이블에서는 기본키를 복합 키로 구성할 필요가 없습니다.
자식테이블에서는 @MapsId만을 이용하면 간단하게 매핑합니다.
@Entity
public class BoardDetail{
@Id
private Long boardId;
@MapsId
@OneToOne
@JoinColumn(name = "BOARD_ID" )
private Board board;
private String content;
...
}
7.3.6 식별, 비식별 관계의 장단점
✅ 식별관계의 단점
- 자식 테이블로 기본키 전파할 때 자식의 기본 키 컬럼이 점점 늘어나므로 기본 키 인덱스가 불필요해짐
- 식별관계를 이용하면 복합 기본 키를 만들어야 하는 경우가 많음
- 식별 관계에서는 자연 키 컬럼을 조합하는 경우가 많음 -> 변경 어려움
- 테이블 구조가 유연하지 못함
✅비식별관계의 장점
- 비식별 관계의 기본 키로 대리키를 사용- > 대리키 생성에 편리한 방법 많음
그러나, 기본 키 인덱스를 활용하기 좋으므로 특정 상황에서는 식별 관계 활용도 적절히!
* 비식별 관계 사용하고 기본 키로 Long 타입의 대리키 사용
( 필수적 비식별 관계 사용 )
7.4 조인테이블
데이터베이스에서 연관관계 설계시 조인컬럼(외래키사용), 조인테이블 사용으로 접근할 수 있습니다.
조인 컬럼을 사용할 때는 외래 키 컬럼만 추가하여 연관관계를 맺으나 조인테이블 방식은 테이블을 추가해 두 테이블의 외래 키를 가지고 연관관계를 관리하므로 외래 키 컬럼이 존재합니다.
조인테이블은 테이블을 추가해야 하므로 관리해야 하는 테이블 수가 늘어납니다.
그러므로 기본 : 조인컬럼 -> 필요 시 조인테이블 이용
조인테이블이 필요할 때는 주로 다대다에서 연결테이블 사용할 때!
'BE > 자바 ORM 표준 JPA 프로그래밍' 카테고리의 다른 글
[JPA STUDY] 6장. 다양한 연관관계 매핑 (0) | 2025.04.13 |
---|---|
[JPA STUDY] 5. 연관관계 매핑 기초 (0) | 2025.04.06 |
[JPA STUDY] 4장. 엔티티 매핑 (0) | 2025.03.30 |
[JPA STUDY] 03. 영속성 관리 (0) | 2025.03.22 |
[ JPA STUDY ] 2장.JPA 시작 (0) | 2025.03.15 |