본문 바로가기

JPA

[JPA] JPA 고급 매핑

 

 

 

상속관계 매핑

JPA는 객체 지향 프로그래밍의 상속을 데이터베이스의 슈퍼타입 서브타입 관계로 매핑할 수 있습니다. 이를 통해 상속 구조를 데이터베이스에 저장할 수 있습니다. 상속관계 매핑에는 여러 전략이 있으며, 각각의 장단점이 있습니다.

주요 어노테이션

  1. @Inheritance(strategy=InheritanceType.XXX)
    • JOINED: 조인 전략
    • SINGLE_TABLE: 단일 테이블 전략
    • TABLE_PER_CLASS: 구현 클래스마다 테이블 전략
  2. @DiscriminatorColumn(name="DTYPE")
    • 상속관계에서 구분 컬럼을 정의할 때 사용합니다.
  3. @DiscriminatorValue("XXX")
    • 각 엔티티가 구분 컬럼에서 가질 값을 정의합니다.

조인 전략 (JOINED Strategy)

장점

  • 테이블 정규화가 잘 되어 데이터 중복을 줄입니다.
  • 외래 키 참조 무결성 제약조건을 활용할 수 있습니다.
  • 저장 공간 효율화에 도움이 됩니다.

단점

  • 조회 시 조인을 많이 사용하게 되어 성능이 저하될 수 있습니다.
  • 조회 쿼리가 복잡해집니다.
  • 데이터를 저장할 때 INSERT SQL이 두 번 호출됩니다.

부모클래스

import javax.persistence.*;

@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn(name = "DTYPE")
public abstract class Item {
    @Id @GeneratedValue
    private Long id;
    
    private String name;
    private int price;

    // getters and setters
}

 

자식클래스

import javax.persistence.*;

@Entity
@DiscriminatorValue("B")
public class Book extends Item {
    private String author;
    private String isbn;

    // getters and setters
}

@Entity
@DiscriminatorValue("A")
public class Album extends Item {
    private String artist;

    // getters and setters
}

이 예제에서는 Item 클래스가 부모 클래스이고, Book과 Album이 자식 클래스입니다. @Inheritance 어노테이션으로 상속 전략을 JOINED로 설정하였습니다. @DiscriminatorColumn은 각 엔티티를 구분하기 위해 "DTYPE" 컬럼을 사용하도록 설정합니다. 각 자식 클래스는 @DiscriminatorValue 어노테이션으로 자신만의 구분 값을 가집니다.

 

단일 테이블 전략 (SINGLE_TABLE Strategy)

단일 테이블 전략은 모든 상속된 엔티티들을 하나의 테이블에 저장하는 방식입니다. 이 전략은 조인이 필요 없어서 조회 성능이 뛰어나지만, 테이블이 커질 수 있습니다.

 

import javax.persistence.*;

@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "DTYPE")
public abstract class Item {
    @Id @GeneratedValue
    private Long id;

    private String name;
    private int price;

    // getters and setters
}

@Entity
@DiscriminatorValue("B")
public class Book extends Item {
    private String author;
    private String isbn;

    // getters and setters
}

@Entity
@DiscriminatorValue("A")
public class Album extends Item {
    private String artist;

    // getters and setters
}

 

장점

  • 조인이 필요 없으므로 일반적으로 조회 성능이 빠름
  • 조회 쿼리가 단순함 

단점

  • 단일테이블에모든것을저장하므로테이블이커질수있다.상황에 따라서 조회 성능이 오히려 느려질 수 있다.
  • 자식 엔티티가 매핑한 컬럼은 모두 null 허용

 

구현 클래스마다 테이블 전략 (TABLE_PER_CLASS Strategy)

이 전략은 각 자식 클래스마다 테이블을 생성하는 방식입니다. 이 전략을 사용하면 각각의 자식 클래스가 자신의 테이블을 가지게 되며, 조인이 필요 없으므로 조회 성능이 좋지만, 테이블 정규화가 안 되어 데이터 중복이 발생할 수 있습니다.

import javax.persistence.*;

@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class Item {
    @Id @GeneratedValue
    private Long id;

    private String name;
    private int price;

    // getters and setters
}

@Entity
public class Book extends Item {
    private String author;
    private String isbn;

    // getters and setters
}

@Entity
public class Album extends Item {
    private String artist;

    // getters and setters
}

 

이 전략은 데이터베이스 설계자와 ORM 전문가 둘 다 추천X 

 

장점

  • 서브 타입을 명확하게 구분해서 처리할 때 효과적
  • not null 제약조건 사용 가능

단점

  • 여러 자식 테이블을 함께 조회할 때 성능이 느림(UNION SQL 필요)
  • 자식 테이블을 통합해서 쿼리하기 어려움

@MappedSuperclass

@MappedSuperclass는 JPA에서 상속관계를 매핑하지 않고, 엔티티들이 공통으로 사용하는 매핑 정보를 모아두기 위해 사용됩니다. 이 어노테이션은 직접 테이블과 매핑되지 않으며, 단순히 매핑 정보를 상속받는 자식 클래스들에 제공하는 역할을 합니다. @MappedSuperclass를 사용하면 부모 클래스의 매핑 정보를 자식 클래스들이 공통으로 사용할 수 있습니다.

주요 특징

  • 엔티티가 아니다: @MappedSuperclass로 지정된 클래스는 JPA 엔티티가 아니므로 데이터베이스 테이블과 직접 매핑되지 않습니다.
  • 테이블과 매핑되지 않는다: 테이블과 매핑되지 않고, 단순히 매핑 정보를 제공하는 역할을 합니다.
  • 공통 매핑 정보 제공: 등록일, 수정일, 등록자, 수정자와 같은 공통 매핑 정보를 모아서 상속받는 자식 엔티티들에 제공할 수 있습니다.
  • 직접 조회 불가: @MappedSuperclass로 지정된 클래스를 직접 조회하거나 검색할 수 없습니다 (em.find(BaseEntity) 불가).
  • 추상 클래스 권장: 직접 생성해서 사용할 일이 없으므로 추상 클래스로 정의하는 것이 좋습니다.
import javax.persistence.*;
import java.time.LocalDateTime;

@MappedSuperclass
public abstract class BaseEntity {
    
    @Column(name = "created_at", updatable = false)
    private LocalDateTime createdAt;
    
    @Column(name = "updated_at")
    private LocalDateTime updatedAt;

    @PrePersist
    protected void onCreate() {
        createdAt = LocalDateTime.now();
    }

    @PreUpdate
    protected void onUpdate() {
        updatedAt = LocalDateTime.now();
    }

    // Getters and Setters
    public LocalDateTime getCreatedAt() {
        return createdAt;
    }

    public void setCreatedAt(LocalDateTime createdAt) {
        this.createdAt = createdAt;
    }

    public LocalDateTime getUpdatedAt() {
        return updatedAt;
    }

    public void setUpdatedAt(LocalDateTime updatedAt) {
        this.updatedAt = updatedAt;
    }
}

 

import javax.persistence.*;

@Entity
public class Member extends BaseEntity {

    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    // Getters and Setters
    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

'JPA' 카테고리의 다른 글

[JPA] JPA 기본값 타입  (0) 2024.06.30
[JPA] JPA 프록시와 연관관계 관리  (0) 2024.06.30
[JPA] JPA 연관관계 매핑 (2)  (0) 2024.06.29
[JPA] JPA 연관관계 매핑 (1)  (0) 2024.06.29
[JPA] JPA 엔티티 매핑  (0) 2024.06.29