RDBMS에서는 객체지향 언어처럼 테이블 상속을 지원하지는 않는다. 하지만 중복되는 테이블에 대해 상속으로 데이터베이스를 설계하고 싶어진다. 이때 3가지의 대표적인 전략이 있다.
Single Table Inheritance
- 하나의 테이블에 모든 테이터를 저장
dtype
으로 구분- 장점 : 조인이 필요없어 성능이 빠름
- 단점 : 대부분 컬럼이 NULL을 허용해야 함, 테이블이 커지고 복잡해짐
Concrete Table Inheritance
- 각 태이블이 완전체
- 별도의 테이블을 모두 생성
- 장점 : 테이블 간 독립적이고 NULL 컬럼 문제 없음
- 단점 : 중복 필드 존재 가능, 다형성 쿼리(여러테이블 조회)어려움
Joined Table Inheritance
- 부모 테이블, 자식 테이블을 나눠서 저장하고 조인하여 조회함
- 장점 : 정규화가 잘 되고 데이터 중복이 없어짐
- 단점 : 쿼리에 조인이 필수적이어서 복잡해지고 성능이 떨어질 수 있음
ORM 에서의 상속 전략
ORM(JPA, Hibernate, …) 에서 위의 3 가지 전략을 가져와 어노테이션으로 지원한다. Java ORM 표준 인터페이스인 JPA를 이용해 자세히 알아보자.
※ ORM (Object-Relational Mapping) : 관계형 데이터를 자동으로 연결해주는 기술
1️⃣ SINGLE_TABLE 전략 (단일 테이블 전략)
📁 Payment.java
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "payment_type")// dtype
public abstract class Payment {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
@Column(name = "amount", nullable = false)
private int amount;
}
📁 CardPayment.java
@Entity
@DiscriminatorValue("CARD")
public class CardPayment extends Payment {
@Column(name="card_number", nullable = false)
private String cardNumber;
}
📁 BankTransferPayment.java
@Entity
@DiscriminatorValue("BANK")
public class BankTransferPayment extends Payment {
@Column(name = "bank_account", nullable = false)
private String bankAccount;
}
📁 TableService.java
@Service
@RequiredArgsConstructor
public class TableService {
private final PaymentRepository paymentRepository;
public void tableExample() {
// 카드 결제 저장
CardPayment cardPayment = new CardPayment(7500, "9876-5432-1098-7654");
paymentRepository.save(cardPayment);
// 은행 이체 결제 저장
BankTransferPayment bankPayment = new BankTransferPayment(12000, "210-6543-0987");
paymentRepository.save(bankPayment);
// 전체 결제 내역 조회
List<Payment> payments = paymentRepository.findAll();
for (Payment payment : payments) {
System.out.println("결제 ID: " + payment.getId() + ", 금액: " + payment.getAmount());
if (payment instanceof CardPayment card) {
System.out.println("카드번호: " + card.getCardNumber());
} else if (payment instanceof BankTransferPayment bank) {
System.out.println("은행계좌: " + bank.getBankAccount());
}
}
}
}
- 모든 결제 정보가 하나의 테이블에 모인다.
payment_type
의 값에서CARD
,BANK
로 구분 된다.CARD
일때bankAccount
은NULL
,BANK
일 때cardNumber
은NULL
값이 된다.- 장점 : JOIN 과정이 없기 때문에 조회 성능이 빠름
- 단점 : NULL 컬럼이 많아져 테이블이 지저분해질 수 있음
2️⃣ JOINED 전략 (조인 전략)
📁 Payment.java
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn(name = "payment_type")// dtype
public abstract class Payment {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
@Column(name = "amount", nullable = false)
private int amount;
}
📁 CardPayment.java
@Entity
public class CardPayment extends Payment {
@Column(name="card_number", nullable = false)
private String cardNumber;
}
📁 BankTransferPayment.java
@Entity
public class BankTransferPayment extends Payment {
@Column(name = "bank_account", nullable = false)
private String bankAccount;
}
- Payment 테이블에 공통 데이터를 넣고 자식 테이블에 추가 데이터를 넣는 방식이다.
- 각각의 자식은 부모 테이블의 id값을 외래키로 갖게 된다.
- 조회할 때 항상 JOIN 이 필요하다.
- 장점 : NULL이 없어 정규화가 잘됨
- 단점 : JOIN 성능 비용 존재함, 특히 조회가 많은 경우 주의
3️⃣ TABLE_PER_CLASS 전략 (구체 테이블 전략)
📁 Payment.java
@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class Payment {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private int id;
@Column(name = "amount", nullable = false)
private int amount;
}
📁 CardPayment.java
@Entity
public class CardPayment extends Payment {
@Column(name="card_number", nullable = false)
private String cardNumber;
}
📁 BankTransferPayment.java
@Entity
public class BankTransferPayment extends Payment {
@Column(name = "bank_account", nullable = false)
private String bankAccount;
}
- 각 자식 클래스마다 테이블을 별도로 생성한다.
- Payment 테이블이 존재하지 않고 amount 컬럼도 중복 저장된다.
- Payment_SEQ : 자식 테이블이 독립적이라 공통된 시퀀스가 필요해 Hibernate가 내부적으로 흉내를 내서 MySQL에서도 시퀀스를 사용하게 해준다
항목 | sequence | auto increment |
---|---|---|
사용방식 | 별도 객체에서 ID 생성 | 테이블 컬럼 자체에서 자동 증가 |
지원 DB | Oracle, PostgreSQL, 등 | MySQL, MariaDB |
유연성 | 여러 테이블에서 공유 가능, 제어 가능 | 단순 자동 증가, 제어 어려움 |
- 장점 : 테이블마다 독립성 보장
- 단점 : Payment 로 전체 조회하면 UNION 쿼리가 발생하기 때문에 느림
📝 요약
항목 | SINGLE_TABLE | JOINED | TABLE_PER_CLASS |
---|---|---|---|
테이블 구조 | 하나의 테이블 | 부모 + 자식 테이블 분리 | 자식 테이블만 존재 |
쿼리 방식 | 단순 SELECT | 부모 기준 JOIN | 자식 테이블 UNION ALL |
타입 구분 방식 | Discriminator 컬럼 필수 (@DiscriminatorColumn ) |
선택 사항 (@DiscriminatorColumn 가능) |
필요 없음 |
장점 | 빠름, 구조 단순 | 정규화, 구조 명확 | 자식 간 완전 독립 |
단점 | NULL 많음, 컬럼 과다 | JOIN 성능 비용 | 조회 느림, 페이징 어려움 |
사용 예시 | 게시글(공지/질문), 결제 등 단순 상속 구조 | 결제(Payment), 직원(정규/계약) 등 구조적 모델링 | 드물게 사용, 시스템 로그/감사 이력 등 완전 분리 필요 |
'Database' 카테고리의 다른 글
그룹함수(Group Function) - Oracle (0) | 2025.05.21 |
---|