JPA를 사용하다 보면 하나의 부모 클래스(슈퍼타입)를 여러 자식 클래스(서브타입)가 상속받는 구조를 자주 만든다.
예를 들어, Item이라는 추상 클래스를 Book, Album, Movie가 상속받는 구조가 그 예다.
객체지향 프로그래밍에서는 이처럼 상속 구조를 활용하는 것이 자연스럽지만,
관계형 데이터베이스에는 상속이라는 개념이 존재하지 않는다.
대신 데이터베이스는 슈퍼타입/서브타입 모델링이라는 방식으로 이 문제를 해결한다.
그리고 JPA는 이 모델링 방식을 그대로 매핑할 수 있도록 다양한 전략을 제공한다.
그러므로 이번 장에서는 JPA의 상속관계 매핑에 대해 정리한다.
각 전략이 어떤 방식으로 테이블을 구성하며, 어떤 장단점을 갖는지, 실무에서는 어떤 전략이 자주 쓰이는지 중심으로 설명한다.
상속관계 매핑이란?
객체의 상속 구조를 데이터베이스의 테이블 구조로 변환하는 방식이다.
JPA에서는 세 가지 전략을 제공한다:
- JOINED 전략 (조인 전략)
- SINGLE_TABLE 전략 (단일 테이블 전략)
- TABLE_PER_CLASS 전략 (구현 클래스마다 테이블 생성)
이 전략들은 @Inheritance(strategy = ...) 애너테이션으로 설정한다.
그리고 구분 컬럼을 사용하기 위해 @DiscriminatorColumn, @DiscriminatorValue도 함께 사용된다.
공통 애너테이션
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn(name = "dtype")
| @Inheritance | 어떤 상속 매핑 전략을 사용할지 지정 |
| @DiscriminatorColumn | 테이블에 저장할 자식 타입 구분 컬럼 이름 지정 |
| @DiscriminatorValue | 각 자식 클래스가 어떤 값을 가질지 지정 (생략하면 클래스명) |
1. JOINED 전략 – 정규화를 지향하는 방식
설명:
부모 클래스와 자식 클래스 각각 테이블을 만든 뒤, 조인으로 데이터를 합쳐 조회하는 방식이다.

예시 코드:
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn(name = "dtype")
public abstract class Item { ... }
@Entity
public class Book extends Item {
private String author;
}
ITEM 테이블
| 1 | JPA책 | 20000 | BOOK |
| 2 | 인셉션 | 15000 | MOVIE |
BOOK 테이블 (자식 전용 테이블)
| 1 | 김영한 | 978-1234567 |
장점
- 테이블이 정규화되어 중복 데이터가 없다.
- 외래 키 무결성 제약조건 설정 가능.
- 저장 공간이 효율적이다.
단점
- 조회할 때 매번 조인을 사용해야 하므로 성능이 떨어질 수 있다.
- INSERT 시 부모 테이블 + 자식 테이블에 각각 저장해야 해서 SQL이 2번 실행된다.
- 쿼리가 복잡해진다.
이 전략은 데이터가 크고 구조가 안정적이며, 정규화가 중요한 도메인에 적합하다.
2. SINGLE_TABLE 전략 – 단일 테이블에 몰아넣기
설명:
모든 자식 클래스의 데이터를 하나의 테이블에 통합해서 저장하는 방식이다.

예시 코드:
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "dtype")
public abstract class Item { ... }
장점
- 조인이 필요 없으므로 조회 성능이 빠르다.
- 쿼리도 단순하다.
- INSERT도 테이블 하나에만 하면 되므로 효율적이다.
단점
- 모든 자식 클래스의 필드를 하나의 테이블에 포함시켜야 하므로, 대부분의 컬럼이 null을 허용해야 한다.
- 테이블이 커지고 컬럼이 많아져서 오히려 성능 저하가 생길 수 있다.
이 전략은 자식 클래스가 많지 않고, 공통 필드가 대부분인 경우에 적합하다. 성능 위주로 단순하게 구성하고 싶을 때 유용하다.
3. TABLE_PER_CLASS 전략 – 자식마다 독립 테이블
설명:
부모 클래스는 테이블 없이 매핑 정보만 제공하고, 각 자식 클래스가 자신의 테이블을 독립적으로 갖는다.
@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class Item { ... }
장점
- 서브타입을 명확하게 분리할 수 있다.
- 자식 테이블에 NOT NULL 제약 조건을 자유롭게 걸 수 있다.
단점
- 부모 타입으로 전체 조회하려면 UNION ALL이 필요하다 → 쿼리 성능이 매우 안 좋다.
- 공통 필드를 반복해서 정의해야 하므로 비효율적이다.
- JPA가 IDENTITY 전략과 호환되지 않는 등 제약이 많다.
이 전략은 거의 사용하지 않는다. JPA 팀도, DB 전문가도 추천하지 않는 전략이다.
@MappedSuperclass – 테이블 없이 공통 필드만 상속
@MappedSuperclass는 상속 매핑과는 다르다.
이건 진짜 상속 관계가 아니라, 단순히 여러 엔티티에 공통 필드를 제공하기 위한 상속 구조다.

- 테이블과 매핑되지 않으며, 자식 클래스가 테이블 생성 시 이 필드들을 포함하게 된다.
- 직접 조회하거나 저장하지 않는다. (em.find(BaseEntity.class)는 불가능)
- @Entity 클래스만 상속받을 수 있다.
- 주로 id, 생성일, 수정일, 작성자 같은 공통 필드를 위한 용도다。
예시 코드:
@MappedSuperclass
public abstract class BaseEntity {
@Id
@GeneratedValue
private Long id;
// 다른 공통 필드도 여기에 추가 가능
}
@Entity
public class Member extends BaseEntity {
private String name;
}
이렇게 하면 Member 클래스는 별도로 @Id를 선언하지 않아도,
BaseEntity로부터 상속받은 id가 식별자(PK)로 사용된다.
마무리하며
상속관계 매핑은 처음 보면 복잡해 보이지만, 각 전략이 어떤 구조로 테이블을 만들고 어떤 식으로 쿼리가 나가는지만 이해하면 생각보다 단순하다.
JOINED는 정규화 지향, SINGLE_TABLE은 성능 지향, TABLE_PER_CLASS는 거의 안 씀.
그리고 공통 필드만 공유하고 싶을 땐 @MappedSuperclass로 처리하면 된다.
결국 중요한 건 지금 내가 어떤 구조를 원하고, 나중에 얼마나 바뀔 수 있느냐다.
잘못 선택하면 나중에 변경이 힘들 수도 있으니, 설계할 때 미리 전략을 잡고 가는 게 좋다.
감사합니다.
'스프링' 카테고리의 다른 글
| [스프링] DTO가 필요한 이유 (3) | 2025.07.27 |
|---|---|
| [스프링] JPA에서 제공하는 쿼리 방법 (1) | 2025.07.26 |
| [스프링] JPA 연관관계 매핑 (2) | 2025.07.25 |
| [스프링] JPA 기본 매핑 어노테이션 (4) | 2025.07.24 |
| [스프링] JPA란? (1) | 2025.07.24 |
