스프링

스프링과 JPA를 이용한 웹개발_상속관계

JANNNNNN 2024. 3. 28. 11:21

상속

  • 상속은 자바에서 중요한 개념
  • 그러나 테이블관점, RDB에서는 상속이라는 개념이 없다.
  • SQL은 상속관계를 위한 구문을 지원하지 않음
  • 그렇다면 RDB를 표현하는 자바객체는 무조건 상속 개념을 사용할 수 없는가?

JPA가 제공하는 상속의 네 가지 전략!

  • Mapped Superclass
  • Table per Class (거의 안씀..)
  • Single Table
  • Joined

Domain Mode

  • 저자(Author)는 여러 건의 글을 게재할 수 있다
  • 글은 책(Book)과 블로그(BlogPost)로 구분된다
  • Book과 BlogPost는 공통사항(Publication)과 고유사항이 있다

1. Mapped Superclass

  • 가장 간단한 전략이다.
  • 각각의 구현클래스(book과 blogpost)를 개별 테이블에 매핑하는 전략
@MappedSuperclass
public abstract class Publication {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = “id”, updatable = false, nullable = false)
    protected Long id;
    
    @Column
    protected String title;
    
    @Version
    @Column(name = “version”)
    private int version;
    …
}

⚠️Publication의 @Entity가 없다~

@Entity(name = “Book”)
public class Book extends Publication {
    @Column
    private int pages;
    …
}
@Entity(name = “BlogPost”)
public class BlogPost extends Publication {

    @Column
    private String url;
    …
}

JPA로 SQL에 넣고 확인해보기!

Mapped Superclass의 장단점

  • 다수의 엔티티가 공통 속성을 공유할 수 있음
  • PK의 경우, 부모 클래스에 지정할 수 있지만 자식 클래스에 지정할 수도 있음
  • mapped superclass가 지정된 클래스는 엔티티가 아니므로 테이블에 매핑되는 것은 아님
  • 이는 다형성을 이용한 쿼리 사용이 어렵다는 의미
    • 예를 들어 blogPost와 Book 종류에 상관 없이 모든 Publication에 대한 쿼리를 실행할 수 없음
  • Author는 blogPost와 Book이라는 두 가지를 구분하여 각각의 연관관계를 유지해야 함
  • 따라서 주로 createdBy, createdOn과 같이 테이블에서 공통적으로 사용되는 반복되는 필드를 각 테이블에 끼워 넣을 때 사용
  • 상속개념을 사용하기 위해서는 뒤에 나오는 세 가지의 inheritance 전략을 고려해야 함

2. Table per Class

  • mapped superclass 전략과 유사(mapped superclass와 같이 blogPost와 Book 테이블 각각을 생성)
  • 가장 큰 차이점은 superclass 또한 엔티티가 됨
  • 권장되는 방법은 아님
  • 공통사항(Publication)이 여기에도 있고 저기에도 있는 중복적인 구조

3. Single Table

  • BlogPost와 Book을 하나의 테이블에 저장하는 방식
  • 운영이 간편
  • 하나의 테이블만 조회하면 되므로 성능 측면에서 가장 유리
  • Null허용 필드가 많아지는 단점이 존재
  • Null 허용과 데이터 무결성은 trade-off관계
  • DBA가 반가워하지 않는 상황

@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)<---
@DiscriminatorColumn(name = “Publication_Type”)
public abstract class Publication {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = “id”, updatable = false, nullable = false)
    protected Long id;
    
    @Column
    protected String title;
    
    @Version
    @Column(name = “version”)
    private int version;
}
@Entity(name = “Book”)
@DiscriminatorValue(“Book”)
public class Book extends Publication {
    @Column
    private int pages;
    …
}
@Entity(name = “BlogPost”)
@DiscriminatorValue(“Blog”)
public class BlogPost extends Publication {
    @Column
    private String url;
    …
}
  • @DiscriminatorColumn(name = “Publication_Type”)
    • 현재의 레코드가 BlogPost에 관한 것인지, Book에 관한 것인지를 구분하기 위해 추가되는 필드
    • 하나의 테이블에 두 가지 주제가 포함되어 있으므로 구분을 위한 추가 필드가 필요
    • 참고) RDB에서는 하나의 테이블은 하나의 주제만 표현하는 것을 권장
  • @DiscriminatorValue
    • DiscriminatorColumn에 표시될 값
    • 예제에서는 각각 Book과 Blog로 표시
  • 값 비싼 연산으로 인식되는 JOIN연산 없이 " Publication과 Book" 혹은 " Publication과 BlogPost " 내용을 조회할 수 있음

➡️하나의 테이블에 있다보니, Book에는 url이 없고, Blog에는 pages가 없어 Null허용 필드가 많아지는 단점이 존재한다!

➡️그러나 하나의 테이블만 가져오면 되기 때문에 다량의 데이터를 다룰 때 성능 측면에서는 유리하다.

4. Joined

  • BlogPost와 Book 테이블을 따로 관리
  • Table per Class와 유사한 구조
  • 가장 큰 차이점 → abstract superclass도 DB 테이블로 매핑(모든 공유 속성을 포함하는 테이블)
    • Publication도 테이블을 가짐

@Entity
@Inheritance(strategy = InheritanceType.JOINED)  <---
public abstract class Publication {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = “id”, updatable = false, nullable = false)
    protected Long id;
    
    @Column
    protected String title;
    
    @Version
    @Column(name = “version”)
    private int version;
    
    @Column
    @Temporal(TemporalType.DATE)
    private Date publishingDate;
    …
}
  • subclass조회에는 superclass와의 join이 반드시 필요 → 쿼리 복잡도 증가
  • subclass 에 NOT NULL 제약 조건을 사용할 수 있다는 장점
  • DiscriminatorColumn을 사용하지 않아도 됨

정리

🤔어떤 전략을 쓸 것인가 ...

  • 성능적으로 중요하고 쿼리 다형성이 필요하다면 → single table
    • table의 크기가 엄청나게 커진다면 항상 좋은 성능을 보인다는 장담을 하지는 못함
    • 또한 NOT NULL 제약조건을 쓸 수 없다는 것은 무결성 관점에서 좋지 않다는 것을 상기해야 함
  • 무결성, 일치성이 중요하다면 → joined
  • 쿼리나 연관관계에서 다형성이 필요없다면 → table per class
    • 위 예제에서 Publication에 대한 쿼리를 실행할 수 없으며 이를 위한 쿼리가 복잡해짐
  • 따라서 single table 혹은 joined이 가장 현실적인 방법

더 알아보자

  • ERD 다이어그램 그리는 방법