쌍문동 척척박사
Spring / ReactJS / PS

상속관계 매핑과 영속성 전이

상속관계 매핑과 영속성 전이

관련 포스트 : 연관관계 매핑


상속 관계 매핑

3.png

RDB에는 상속 관계라는 개념이 존재하지 않는다. 하지만 슈퍼타입-서브타입이라는 객체의 상속과 유사한 개념이 있어 해당 모델링 기법을 이용해 객체의 상속관계를 매핑할 수 있다.

슈퍼타입-서브타입 논리 모델을 실제 물리 모델로 구현하는 방법은 3가지가 있다. 참고로 각 전략은 테이블의 설계 전략으로, 객체는 설계 전략에 관계없이 모두 똑같은 상속 구조를 가진다.

  • 조인 전략
  • 단일 테이블 전략
  • 구현 클래스마다 테이블 전략

1. 조인 전략

조인 전략은 조상 객체와 자식 객체를 각각 슈퍼 타입과 서브타입에 맞춰 변환하는 전략이다. 객체 호출 시 슈퍼 타입과 서브 타입을 조인해 가져온다.

4.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 조상
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn  // 자식 타입 구별자
@Entity
public abstract class Item {

    @Id
    @GeneratedValue
    @Column(name="ITEM_ID")
    private Long id;

    private String name;
    private int price;
    private int stockQuantity;
}
1
2
3
4
5
// 자식
@Entity
public class Album extends Item {
    private String artist;
}
1
em.find(Album.class, 101L); // 자동으로 조인함

장점

  • 테이블이 정규화됨
  • 외래키 참조 무결성 제약조건 활용 가능
  • 저장공간 효율화

단점

  • 조회 시 조인을 많이 사용. 성능 저하
  • 조회 쿼리가 복잡함
  • 데이터 저장 시 INSERT SQL 2번 호출

생각보다 큰 단점이 없어서 가장 정석적인 방법이다.

2. 단일 테이블 전략

모든 객체 정보를 한 테이블에 선언하는 전략이다. 각 객체마다 구분짓기 위해서 DTYPE 사용이 필수다.

5.png

1
2
3
// 조상
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
// 이하 동일

장점

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

단점

  • 자식 엔티티에서 사용하지 않는 컬럼은 null이 됨
  • 단일 테이블에 모든 데이터를 저장하므로 테이블이 커짐. 상황에 따라 조회 성능이 오히려 더 느려질 수 있음

전략 자체보다도 전략을 변경하는 부분이 눈에 띈다. 성능 개선이 필요해 테이블 구조의 변경이 필요하면 그냥 InheritanceType만 바꾸면 된다는 점에서 JPA의 장점이 드러난다.

구별자가 되는 컬럼의 이름을 직접 지정하고 싶을 땐 name 속성을 이용하자.

1
2
@DiscriminatorColumn(name="DTYPE")  // 기본값
public class Item {}

각 엔티티마다 다른 구별자 값을 갖고 싶으면 @DiscriminatorValue를 사용하자.

1
2
@DiscriminatorValue("A")  // 기본값 : 엔티티 명
public class Book extends Item {}

3. 구현 클래스마다 테이블 전략

테이블을 모두 따로 두는 전략이다. DBA와 개발자 모두 추천하지 않는 전략이다.

6.png

@MappedSuperclass

상속과 비슷해 보이지만 다른 개념으로, 공통 매핑 정보가 필요할 때 사용하는 어노테이션이다.

7.png

1
2
3
4
5
@MappedSuperclass
public abstract class BaseEntity {
    private LocalDateTime createdDate;
    private LocalDateTime lastModifiedDate;
}
1
2
3
4
5
6
7
public abstract class Item extends BaseEntity{
    @Id
    @GeneratedValue
    @Column(name="ITEM_ID")
    private Long id;
    // ...
}
  • 엔티티도 아니며, 테이블과 매핑되지 않는 객체다.
  • 자식 클래스에 매핑 정보를 제공하는 역할로, 자식 테이블에는 해당 컬럼이 추가된다.
  • 추상 클래스로 선언하는 것을 권장한다.
  • 테이블이 아니므로 조회도 불가능하다.

얼핏 보면 상속에서 구현 클래스마다 테이블 전략과 비슷해 보이지만, 상속은 서로 관련 있는 엔티티들의 공통 부분을 묶어 조상을 만든 것이고, 공통 매핑 정보는 서로 관련이 없지만 공통 부분이 있는 엔티티들의 부분을 말한다.

ex) 물건-앨범 (상속) / 게시글-댓글 (공통 매핑 정보 가짐)

참고

엔티티는 같은 @Entity@MappedSuperclass로 지정된 클래스만 상속 가능하다.

영속성 전이 (CASCADE)

영속성 전이는 특정 엔티티를 영속 상태로 만들 때 연관된 엔티티도 함께 영속 상태로 만드는 것을 말한다.

8.png

1
2
3
4
5
6
7
8
9
public class Parent{
    @OneToMany(mappedBy = "parent", cascade=CascadeType.ALL)
    private List<Child> childList = new ArrrayList<>();

    public void addChild(Child child){
        childList.add(child);
        child.addParent(this);
    }
}
1
2
3
4
5
6
7
8
9
public class Child {
    @ManyToOne
    @JoinColumn(name="PARENT_ID")
    private Parent parent;

    public void addParent(Parent parent){
        this.parent = parent;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
Child child1 = new Child();
Child child2 = new Child();

Parent parent = new Parent();
parent.addChild(child1);
parent.addChild(child2);

em.persist(parent);
// 새로 생성된 child도 자동으로 persist됨
// em.persist(child1);
// em.persist(child2);
em.remove(parent);  // child도 모두 remove됨
  • CasecadeType으로 전이 타입을 정한다.
  • PERSIST : 객체 저장 시 연관 객체를 모두 저장함
  • REMOVE : 객체 삭제 시 연관 객체를 모두 삭제함
  • ALL : 모든 옵션 적용
  • 나머지는 잘 사용하지 않음
  • 영속성 전이는 연관관계 매핑과 아무 관련이 없다.
  • 엔티티를 영속화할 때 연관된 엔티티도 함께 영속화하는 편리함을 제공할 뿐이다.

주의 (영속성 전이를 사용할 수 없는 상황)

  • Parent 외에도 다른 소유자가 있어 여러 객체가 Child를 관리하는 상태인 경우
  • 라이프 사이클이 다른 경우

주의

단방향 연관관계에서 영속성 전이를 사용하면 insert 후에 update 쿼리가 추가로 발생하기 때문에 영속성 전이가 필요한 경우 양방향 연관관계로 설정하는 것이 좋다.

고아 객체

JPA는 부모 엔티티와 연관관계가 끊어진 자식 엔티티 (고아 객체)를 자동으로 삭제하는 기능을 제공한다.

1
2
@OneToMany(mappedBy = "parent", cascade=CascadeType.PERSIST, orphanRemoval = true)
private List<Child> childList = new ArrayList<>();
1
2
Parent p = em.find(Parent.class, parent.getId());
p.getChildList().remove(0);  // Child DELETE 쿼리가 실행됨
  • 영속성 전이와 마찬가지로 참조하는 곳 (소유자)이 하나일 때만 사용해야 한다.
  • @OneToOne, @OneToMany 만 사용 가능
  • 부모 엔티티가 아예 삭제되는 경우엔 CasecadeType.REMOVE와 동일하게 동작한다. (모두 삭제)

영속성 전이 + 고아 객체, 생명주기

  • CasecadeType.ALLorphanRemoval=true 옵션을 모두 활성화
  • 부모 엔티티를 통해 자식 엔티티의 생명 주기를 관리할 수 있다.
  • 도메인 주도 설계 (DDD)의 Aggregate Root 개념을 구현할 때 유용하다.

참고 자료

자바 ORM 표준 JPA 프로그래밍

comments powered by Disqus