아 상속 계층 전략별로 예시들어가면서 테이블 만들었는데 안이뻐서 지웠다. 시간 박았는데 속상하다
객체 지향에서는 각 클래스끼리의 상속 관계가 존재한다.
그러나 관계형 데이터베이스에서는 상속을 지원하지 않는다.
대신 super 타입, sub 타입이라는 모델링 기법을 통해 객체의 상속 관계를 매핑할 수 있다.
Entity 계층 구조에 따른 상속 관계 매핑
부모 테이블과 특수한 유형을 표현하는 자식 테이블
JPA에서는 이를 지원하기 위해 객체의 상속을 이용한 @Inheritance 어노테이션을 지원한다.
엔티티 상속 계층 전략
- SINGLE_TABLE (default)
- 부모와 모든 자식 엔티티의 속성을 포함하는 하나의 테이블 사용
- TABLE_PER_CLASS
- 부모와 자식 엔티티를 개별적인 테이블로 나누어 사용
- JOINED
- 부모와 각 자식 엔티티를 개별 테이블로 나누되, 서로 외래키로 연결
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@Getter
public abstract class Item {
@Id
@GeneratedValue
@Column(name = "item_id")
private Long id;
private String name;
private int price;
private int stockQuantity;
}
JPA 어노테이션을 통한 자식 엔티티 구분
@DiscriminatorColumn :부모에 자식 엔티티를 구분할 컬럼을 추가해주는 어노테이션 (default = DTYPE)
@DiscriminatorValue : 자식 엔티티에서 컬럼에 구분되는 명칭을 변경하는 어노테이션
default로 자식 엔티티명이 저장된다.
- 부모 엔티티 : @DiscriminatorColumn(name = "ITEM_TYPE")
- 자식 엔티티 : @DisciminatorValue(value = "M")
엔티티 상속 계층 전략
JOINED (조인 전략)
부모 엔티티와 각 자식 엔티티마다 별도의 테이블로 관리하는 방법
super 타입의 테이블은 부모 엔티티의 속상만을 포함하고, 각 sub 타입의 테이블은 각 sub타입의 속성과 super의 식별자를 포함한다.
Join을 통해 테이블을 정규화하여 저장 공간을 효율적으로 사용할 수 있고, 관계에 의해 발생하는 복잡한 참조 무결성 규칙을 적용할 수 있다. → 유연성과 확장성
조회시에도 조인해야해서 성능 문제는 있을 수 있고, 스키마가 복잡해져서 저장할때 insert 쿼리가 두번 수행된다는 단점이 있다.
자식 엔티티를 저장하면 자동으로 부모 엔티티도 저장한다.
SINGLE_TABLE (싱글 테이블 전략)
부모와 자식 엔티티들의 속성을 단일 테이블로 관리하는 방법
조인할 필요가 없어서 간단하고 직관적이다. → 조회 성능이 좋음
다만, 자식 엔티티가 많아지면 테이블 크기가 커지고 null값을 가지는 속성이 많아지는 단점이 있다.
TALBE_PER_CLASS (테이블당 클래스 전략)
각 자식 엔티티들이 부모의 모든 속성을 포함해서 생성하되, 부모 테이블은 생성되지 않는 방법
ORM 입장에서야 상속을 사용하지만 DBMS는 어떠한 관계도 없기에 사용을 추천하지 않는다.
엔티티간의 공통 속성이 존재하는 경우 - @MappedSuperclass
상속을 단순히 속성을 재사용하기 위해 사용하는 경우와 상속 관계의 매핑을 헷갈리면 안된다.
상위 객체가 하위 객체를 종속시키는 상속이 아니라, 단순히 공통 속성들만 재사용할 뿐이다.
상위 클래스는 공통 속성의 집합일 뿐이니 추상 클래스로 만들길 권장하며, 당연히 상위 클래스로 조회, 검색할 수 없다.
Spring Data JPA에서의 Repository 활용
JPA에서는 @Inheritance 전략과 함께 instanceof를 사용하여 자식 클래스의 타입도 추론할 수 있다.
캐스팅하면 자식 클레스에만 있는 필드값도 조회할 수 있다.
프로젝트에서 상속 엔티티를 처리할 때 겪은 문제
우선 프로젝트에서 사용하는 엔티티의 관계는 다음과 같다.

super 타입인 Problem 엔티티 아래에 sub 타입으로 wargame, assignment, algorithm 유형의 엔티티가 존재한다.
Instanceof를 통한 클래스 검사
private Problem createProblemByType(RegisterProblemDto registerProblemDto) {
switch (registerProbelmDto.getType()) {
case WARGAME:
if (registerProblemDto instanceof RegisterWargameProblemDto wargameDto) {
return createWargameProblem(wargameDto);
} else {
throw new IllegalArgumentException("Expected RegisterWargameProblemDto but found: " + registerProblemDto.getClass().getName());
}
case ASSIGNMENT:
return new AssignmentProblem();
case ALGORITHM:
return new AlgorithmProblem();
default:
throw new IllegalArgumentException("Unsupported problem type: " + registerProblemDto.getType());
}
}
type에 따라서 ProblemType을 구분한 뒤에 자식 클래스(RegisterWargameProbelmDto)로 캐스팅하려고 했는데 오류가 생겼다
type이 null이라고 함
그래서 난 Enum을 역직렬화하는데 실패했나?
Jackson 역직렬화쪽을 파면서 JSON 프로퍼티가 문제라고 생각하고 있었다...
private Problem createProblemByType(RegisterProblemDto registerProblemDto) {
if (registerProblemDto instanceof RegisterWargameProblemDto wargameDto) {
return createWargameProblem(wargameDto);
} else if (registerProblemDto instanceof RegisterAlgorithmProblemDto algorithmDto) {
return new AlgorithmProblem();
} else if (registerProblemDto instanceof RegisterAssignmentProblemDto assignmentDto) {
return new AssignmentProblem();
} else {
throw new IllegalArgumentException("Unsupported problem type: " + registerProblemDto.getClass().getName());
}
}
더 직관적이고 Instanceof 검사도 정상적으로 동작하도록 해결할 수 있었다.
'mysql' 카테고리의 다른 글
| MySQL 8.0의 GTID 복제(Replication) 및 mode 변경 실습 (0) | 2025.06.18 |
|---|---|
| Master-Slave 노드간 복제(Replication) 실습 (feat. MySQL 8.0) (1) | 2025.06.18 |
| MySQL Full-text Search가 항상 LIKE보다 뛰어난 성능을 내는가? (3) | 2025.06.13 |
| 데이터베이스 인덱스는 왜 B-tree를 사용하는가? (0) | 2025.03.12 |
| 토이프로젝트 - 트랜잭션 관리를 통한 베타락(Exclusive Lock) 구현 (0) | 2024.11.22 |