[JPA] 연관관계 매핑(3) - 상속관계 매핑

상속관계 매핑

객체와 달리 RDB에는 상속관계가 존재하지 않는다. 그래서 DB에서는 슈퍼타입-서브타입 관계라는 모델링 기법을 이용해서 객체의 상속관계를 나타낸다. 코드상에서 상속관계 설정은 부모클래스에 @Inheritance 어노테이션을 추가함으로서 설정 가능하다.

1. 부모테이블과 자식테이블을 JOIN하여 연관관계를 맺는다.
2. 부모, 자식 모두를 한개의 테이블로 합치고 TYPE정보를 통해 구분한다.
3. 자식테이블만 만들고 부모테이블의 필드를 포함시켜준다.

 

1. JOIN을 통한 연관관계 매핑 : @Inheritance(strategy = InheritanceType.JOINED)

 부모, 자식객체 각자의 데이터를 저장하는 대신, 자식객체의 테이블에 부모객체의 id를 fk로 두고 관리한다. 이때 jpa로 자식클래스의 데이터를 조회하면 부모클래스 테이블과 join을 통해서 전체 정보를 가져온다.

그리고 위의 ITEM클래스를 보면 DTYPE라는것이 있다. 물론 JOIN하면 어떤 자식 클래스인지 알 수 있기야 하겠지만, DB의 쿼리만 보아서는 알 수 없기 때문에 @DiscriminatorColumn을 클래스에 어노테이션 추가하여 DTYPE을 설정해주자. 추가적으로 @DiscriminatorValue("~")를 이용하면 DTYPE의 이름을 ~로 설정해줄 수 있다.

 

JOIN을 이용한 매핑은 추가적인 쿼리문이 사용되어 성능이 저하되고 복잡하다는 단점도 있지만, 저장공간의 효율성과 정규화를 통한 객체지향적 설계가 가능한 방법이기 때문에 통상적으로 널리 사용되는 방법이다.

//부모클래스
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn
public class Item {
...
}


//자식클래스
@Entity
@DiscriminatorValue("M")
public class Movie extends Item {
...
}

 

2. 단일 테이블을 통한 연관관계 매핑 : @Inheritance(strategy = InheritanceType.SINGLE_TABLE)

 부모와 자식객체의 필드들을 모두 한 테이블에 저장하는 방법이다. 서비스 규모가 크지 않고, 간단하게 관리하고 싶을때는 이러한 방법으로 구분하는 것이 성능적으로 이득을 볼 수 있기 떄문이다. (쿼리문의 통신이 한번만 일어나며, JOIN쿼리문을 추가적으로 필요로 하지 않기 때문)

대신 단일 테이블 전략에서는 DTYPE이 필수적으로 들어가야 어떤 자식타입 테이블객체인지 구분이 가능하다. 

물론 JOIN을 이용한 매핑에서도 DTYPE은 존재하는 것이 바람직하다.

 

3. 자식 클래스 테이블에 부모클래스 필드 포함 : @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)

 JOIN을 이용한 연관관계 매핑 방법과 유사하지만, 부모클래스를 추상클래스로 선언해주고, 그에 따라 ITEM 테이블이 존재하지 않는다. 또한 DTYPE을 필요로 하지 않음을 알 수 있다. 이러한 방법은 단순히 값을 추가/삭제시에는 좋지만, 부모타입으로 접근하는 등의 객체지향성을 사용한 코드에서는 자식코드들을 모두 UNION해서 조회해야 하는 등 매우 비효율적으로 동작하게 된다. 또한 테이블상 상속관계가 나타나지 않아 조회, 변경 등 다양한 경우 성능적 손해를 보기 때문에 잘 사용되지 않는 방법이다.

 

 

상속관계 매핑 정리

 jpa를 이용한 설계에서는 테이블 구조의 변경이 일어났는데, 코드레벨에서 테이블에 관련된 코드들을 다시 다 작성하는 것이 아닌, 어노테이션의 옵션만 수정해주는 방법을 이용하였다. 만약 jpa를 이용하지 않았더라면 테이블 구조의 변경과 같은 계획에 수정이 생겼을 때 코드에 많은 수정이 이루어져야 할 것이다,

 세 가지 중에서 기본적으로 JOIN을 이용한 매핑을 사용하고, DB가 가볍고 단순한 경우에만 성능적 이점을 위해 단일테이블 매핑을 고려하자. 부모클래스의 필드를 자식클래스에 포함하는 방법은 사용하지 말자.

 

 

추가

@MappedSuperclass

 객체들간에 공통 매핑 정보가 존재할때, 공통정보를 "객체에서" 상속받아서 사용할 수 있다. (상속관계 매핑이 아니며, 테이블과는 관계가 없다.)

예를 들어, 모든 테이블에 수정 정보(수정한 사람, 마지막 수정시간 등)가 들어가야 된다고 가정하자. 그러면 개발자는 모든 객체에 수정 정보 필드와 메서드를 집어넣어주어야 한다. 이때 코드를 직접 일일히 작성하는 대신 이 어노테이션을 이용하면 된다.

//@Entity << 엔티티가 아니다!
@MappedSuperclass //매핑정보만 넘겨주는 부모클래스
public abstract class BaseEntity {

    @Column(name = "EDIT COLUMNNAME") //컬럼명변경도 가능
    private String createdBy;
    private LocalDateTime createdDate;
    private String lastModifiedBy;
    private LocalDateTime lastModifiedDate;
    
    //getter, setter
}

@Entity
public class 기존객체 extends BaseEntity {
	...
}

 

//Main함수 활용
    ...
    기존객체 a = new 기존객체();
    a.setCreatedBy("Kim");
 
    em.persist(a);
    
    em.flush();
    em.clear();
    
    tx.commit();
    ...

객체 상속관계를 설정하고 어노테이션을 추가해주면 테이블에는 각 테이블에 컬럼이 추가된 것 처럼 생성된다. 이때 어노테이션이 들어간 클래스는 엔티티가 아니며, 단순히 상속받는 자식클래스에 정보만 제공하는 역할을 한다. 따라서 추상클래스로 선언하는 것이 좋겠다.