테이블 전략
📌 JPA에서 엔티티 상속 구조를 데이터베이스 테이블에 매핑하는 방법을 말한다. JPA는 엔티티의 상속 구조를 처리하기 위해 3가지의 테이블 전략을 제공하며 각각의 전략은 데이터 저장 방식과 성능에 차이가 있으므로 프로젝트의 요구사항에 맞게 선택할 수 있다.
- 관계형 데이터베이스의 테이블에는 상속 관계가 없다.
dtype
@DiscriminatorColumn의 dtype은 **JPA (Java Persistence API)**에서 **싱글 테이블 상속 전략(Single Table Inheritance)**을 사용할 때, 엔티티의 타입을 구분하는 데 사용되는 컬럼을 의미합니다. dtype 컬럼은 테이블 내의 데이터가 어떤 엔티티 타입에 해당하는지 식별하기 위해 자동으로 생성됩니다.
+ 엔티티 : 데이터베이스 테이블과 매핑되는 자바 클래스
@DiscriminatorColumn의 역할
- 싱글 테이블 상속 전략에서 엔티티 유형을 구분하기 위해 사용.
- 싱글 테이블 상속 전략: 하나의 테이블에 상속 관계를 가진 모든 엔티티 데이터를 저장.
- 각 엔티티 타입을 식별할 수 있도록 테이블에 구분 컬럼(Discriminator Column)이 필요.
- 기본적으로 DiscriminatorColumn의 이름은 **DTYPE**이며, 이는 JPA의 관례입니다.
- @DiscriminatorColumn을 사용하면 이 컬럼의 이름이나 기타 설정을 변경할 수 있습니다.
기본 dtype 컬럼의 생성
예를 들어, 다음과 같은 상속 구조가 있다고 가정합니다.
엔티티 상속 구조
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "dtype")
public abstract class Animal {
@Id
@GeneratedValue
private Long id;
private String name;
}
@Entity
@DiscriminatorValue("DOG")
public class Dog extends Animal {
private String breed;
}
@Entity
@DiscriminatorValue("CAT")
public class Cat extends Animal {
private int livesLeft;
}
결과 테이블
이 코드로 인해 데이터베이스에 다음과 같은 테이블이 생성됩니다:
ID NAME dtype BREED LIVESLEFT
1 | Max | DOG | Husky | NULL |
2 | Whiskers | CAT | NULL | 9 |
- dtype: 데이터를 어떤 엔티티(Dog, Cat)로 매핑할지 식별하기 위한 컬럼.
- DiscriminatorValue로 지정한 값이 dtype 컬럼에 저장됩니다.
@DiscriminatorColumn의 주요 속성
- name:
- dtype 컬럼의 이름을 설정합니다.
- 기본값: "DTYPE".
- length:
- dtype 컬럼의 길이를 설정합니다.
- 기본값: 31.
- discriminatorType:
- 컬럼의 데이터 타입을 설정합니다.
- 기본값: STRING.
- 다른 옵션: CHAR, INTEGER.
예: 이름과 타입 변경
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "animal_type", discriminatorType = DiscriminatorType.STRING, length = 50)
public abstract class Animal {
@Id
@GeneratedValue
private Long id;
private String name;
}
결과:
ID NAME animal_type BREED LIVESLEFT
1 | Max | DOG | Husky | NULL |
2 | Whiskers | CAT | NULL | 9 |
Discriminator 컬럼이 필요한 이유
- JPA는 테이블의 데이터를 읽어 올 때, dtype 컬럼의 값을 기준으로 어떤 서브클래스(엔티티)로 변환할지 결정합니다.
- @DiscriminatorValue에 지정된 값을 사용하여 데이터를 매핑합니다.
기본값: DTYPE
- JPA는 관례적으로 **DTYPE**이라는 이름을 기본값으로 사용합니다.
- @DiscriminatorColumn을 명시하지 않으면 DTYPE 컬럼이 자동으로 생성됩니다.
정리
- dtype 컬럼:
- 싱글 테이블 상속 전략에서 엔티티 타입을 식별하기 위한 컬럼.
- JPA의 기본 관례로 이름은 DTYPE.
- @DiscriminatorColumn:
- dtype 컬럼의 이름, 데이터 타입, 길이 등을 변경할 수 있는 어노테이션.
- 사용 이유:
- 테이블 내 데이터와 엔티티 타입 간의 매핑을 정확히 하기 위해 필요.
JPA의 테이블 전략
📌 JPA는 모든 전략으로 테이블을 구현할 수 있도록 지원한다.
Annotation
- @Inheritance(strategy = InheritanceType.${전략})
- JOINED : 조인
- SINGLE_TABLE : 단일 테이블(Default)
- TABLE_PER_CLASS : 구현 클래스
- @DiscriminatorColumn(name = "dtype")
- dtype 컬럼을 생성한다(관례).
- 이름 변경이 가능하다.
- 기본 값 : DTYPE
- @DiscriminatorValue("${값}")
- dtype 값 지정
- 기본 값 : 클래스 이름
@Entity
@Table(name = "product")
@DiscriminatorColumn(name = "dtype")
public abstract class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private BigDecimal price;
public Product() {
}
public Product(String name, BigDecimal price) {
this.name = name;
this.price = price;
}
}
@Entity
@Table(name = "book")
@DiscriminatorValue(value = "B")
public class Book extends Product {
private String author;
public Book() {
}
public Book(String author, String name, BigDecimal price) {
super(name, price);
this.author = author;
}
}
@Entity
@Table(name = "coat")
@DiscriminatorValue(value = "C")
public class Coat extends Product {
private Integer size;
}
public class Main {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("entity");
EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
transaction.begin();
try {
Book book = new Book("wonuk", "spring-advanced", BigDecimal.TEN);
em.persist(book);
transaction.commit();
} catch (Exception e) {
transaction.rollback();
} finally {
em.close();
}
emf.close();
}
}
JOINED
@Entity
@Table(name = "product")
@DiscriminatorColumn(name = "dtype")
@Inheritance(strategy = InheritanceType.JOINED)
public abstract class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private BigDecimal price;
public Product() {
}
public Product(String name, BigDecimal price) {
this.name = name;
this.price = price;
}
public Long getId() {
return id;
}
}
public class Main {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("entity");
EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
transaction.begin();
try {
Book book = new Book("wonuk", "spring-advanced", BigDecimal.TEN);
em.persist(book);
em.flush();
em.clear();
Book findBook = em.find(Book.class, book.getId());
transaction.commit();
} catch (Exception e) {
transaction.rollback();
} finally {
em.close();
}
emf.close();
}
}
TABLE_PER_CLASS
@Entity
@Table(name = "product")
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class Product {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
private BigDecimal price;
public Product() {
}
public Product(String name, BigDecimal price) {
this.name = name;
this.price = price;
}
public Long getId() {
return id;
}
}
TABLE_PER_CLASS 문제점
public class Main {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("entity");
EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
transaction.begin();
try {
Book book = new Book("wonuk", "spring-advanced", BigDecimal.TEN);
em.persist(book);
em.flush();
em.clear();
Product findProduct = em.find(Product.class, book.getId());
transaction.commit();
} catch (Exception e) {
transaction.rollback();
} finally {
em.close();
}
emf.close();
}
테이블 전략 장단점
- JOINED
- 장점
- 테이블 정규화
- 외래 키 참조 무결성
- 저장공간 효율
- 단점
- 조회시 JOIN을 많이 사용한다.
- 데이터 저장시 INSERT SQL 이 2번 호출된다.
- SQL Query가 복잡하여 성능이 저하될 수 있다.
- 장점
- SINGLE_TABLE
- 장점
- JOIN을 사용하지 않는다.
- 실행되는 SQL이 단순하다.
- 단점
- 자식 Entity가 매핑한 컬럼은 모두 null을 허용한다.
- 단일 테이블에 모든 것을 저장하므로 테이블이 커질 수 있다.
- 상황에 따라서 조회 성능이 오히려 느려질 수 있다.
- 장점
- TABLE_PER_CLASS
- 테이블끼리 연관짓기 힘들다, 사용하지 않는것을 권장한다.
- 장점
- 자식 클래스를 명확하게 구분해서 처리할 수 있다.
- not null 제약조건 사용이 가능하다.
- 단점
- 여러 자식 테이블을 함께 조회할 때 성능이 느리다.
- 부모 객체 타입으로 조회할 때 모든 테이블을 조회해야 한다.
- 선택 기준
- 비지니스적으로 복잡하다 = JOINED
- 객체 지향적인 개발에 어울리는 방법
- 단순하고 확장 가능성이 없다 = SINGLE_TABLE
- 두 방법의 장단점을 구분하여 상황에 맞는 선택을 해야한다.
- 비지니스적으로 복잡하다 = JOINED
'DB 접근 > JPA ( Java Persistence API )' 카테고리의 다른 글
지연로딩, 즉시로딩 (0) | 2025.01.15 |
---|---|
Proxy (0) | 2025.01.14 |
연관관계 (0) | 2025.01.12 |
[JPA] Spring Data JPA (0) | 2025.01.07 |
[JPA] 연관관계 Mapping (0) | 2025.01.06 |