프로그래밍/Spring

[ JPA ] 상속관계 매핑

Yanoo 2022. 1. 15. 15:27
728x90
반응형

<인프런의 김영한님의 강의를 보고 정리한 내용입니다>

🎈 상속관계 매핑

우선 객체는 상속관계가 있지만 데이터 베이스는 상속 관계가 없다. 그나마 비슷한 것이 관계형 데이터베이스의 슈퍼타입과 서브타입관계라는 모델링 기법인데, 상속관계 매핑이란 객체의 상속 구조와 DB의 슈퍼타입 서브타입 관계를 매핑하는 것이다.

이런 논리적 모델을 봤을 때 물품이라는 슈퍼타입은 id, 이름, 가격과 같은 공통된 특징을 가지고, 서브타입은 각각의 특징을 가지게 된다.

객체의 입장에서 봤을 때는, 명확하게 상속관계가 있다.

 

🎈 물리 모델로 구현

이제 이 슈퍼타입, 서브타입 논리 모델을 실제 물리 모델로 구현하는 방법은 총 3가지가 있다.

  1. 각각 테이블로 변환 -> 조인 전략 
  2. 통합 테이블로 변환 -> 단일 테이블 전략
  3. 서브타입 테이블로 변환 -> 구현 클래스마다 테이블 전략

JPA는 똑같은 객체 상속에 대해 이 3가지 모두를 지원해준다.

 

먼저 코드를 봐보면

  • Item 
  • import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; @Entity public class Item { @Id @GeneratedValue private Long id; private String name; private int price; }​
  • Album
    import javax.persistence.Entity;
    
    @Entity
    public class Album extends Item{
    
        private String artist;
    }
  • Movie
    import javax.persistence.Entity;
    
    @Entity
    public class Movie extends Item{
    
        private String director;
        private String actor;
    }​
  • Book
    import javax.persistence.Entity;
    
    @Entity
    public class Book extends Item{
    
        private String author;
        private String isbn;
    }​

이렇게 한 후 실행을 해보면,

여기서 알 수 있는 점은 기본 전략이 3가지중에서 단일 테이블 전략이라는 점이다.

전략을 바꾸는 법은 @Inheritane 어노테이션을 사용하면 된다.

@Inheritance(strategy = InheritanceType.JOINED)

@Inheritance(strategy = InheritanceType.SINGLE_TABLE) - 기본 값

@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) - 비추천

예시를 보면

 

 

🎁 각각 테이블로 변환 -> 조인 전략

Item에 어노테이션을 추가한다.

  • Item
import javax.persistence.*;

@Entity
@Inheritance(strategy = InheritanceType.JOINED) // 이 부분 추가
public class Item {

    @Id @GeneratedValue
    private Long id;

    private String name;
    private int price;
}

결과를 보면

올바르게 생성된 것을 알 수 있다.

데이터를 삽입해서 확인해보면

  • JpaMain
    import javax.persistence.EntityManager;
    import javax.persistence.EntityManagerFactory;
    import javax.persistence.EntityTransaction;
    import javax.persistence.Persistence;
    
    public class JpaMain {
        public static void main(String[] args) {
            EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
    
            EntityManager em = emf.createEntityManager();
    
            EntityTransaction tx = em.getTransaction();
            tx.begin();
    
            try {
    
                Movie movie = new Movie();
                movie.setDirector("aaaa");
                movie.setActor("bbbb");
                movie.setName("바람과함께사라지다");
                movie.setPrice(10000);
    
                em.persist(movie);
    
                em.flush();
                em.clear();
    
                Movie findMovie = em.find(Movie.class, movie.getId());
                System.out.println("findMovie = " + findMovie);
    
                tx.commit();
            } catch (Exception e) {
                tx.rollback();
            } finally {
                em.close();
            }
    
            emf.close();
        }
    }​

조인해서 데이터를 가져오는 것을 알 수 있다.

여기서 ITEM테이블에 DTYPE을 안넣어서 추가해야하는데 DTYPE은 종류라고 생각하면 된다.

추가하려면 @DiscriminatorColumn을 추가하면 된다.

코드로 보면

import javax.persistence.*;

@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn // 이 부분 추가
public class Item {

    @Id @GeneratedValue
    private Long id;

    private String name;
    private int price;

    //... 이하 생략
}

결과를 보면

추가된 것을 확인할 수 있다.

 

 

 

🎁 통합 테이블로 변환 -> 단일 테이블 전략

Item에 어노테이션을 수정한다.

import javax.persistence.*;

@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE) // 이 부분 추가
@DiscriminatorColumn // 단일 테이블은 이 부분 생략해도 된다.
public class Item {

    @Id @GeneratedValue
    private Long id;

    private String name;
    private int price;

    // ... 이하 생략
}

 

 

 

🎁 서브타입 테이블로 변환 -> 구현 클래스마다 테이블 전략

Item에 어노테이션을 추가하고 추상클래스로 바꿔야한다.

import javax.persistence.*;

@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) // 이 부분 추가
@DiscriminatorColumn // 여기에선 의미가 없음
public abstract class Item { // 원래 추상클래스로 만들어야함

    @Id @GeneratedValue
    private Long id;

    private String name;
    private int price;

   // ... 이하 생략
}

사실 전략들도 추상 클래스로 바꿔야하는데 추상 클래스로 하지 않으면 Item 혼자 독단적인 테이블을 사용한다는 의미므로 사용하면 안된다. 즉 이 전략을 사용할 경우 Item 테이블은 필요가 없는데 추상클래스로 만들지 않으면 이 Item 테이블이 생성된다.

 

하지만 이 전략은 치명적인 단점이 있다. 데이터를 삽입할 때는 문제가 되지 않는데 찾을 때에 있다.

Item item = em.find(Item.class, movie.getId());

객체 지향 관점에서 이렇게 부모클래스로 조회할 수 있어야 하는데,

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;

public class JpaMain {
    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");

        EntityManager em = emf.createEntityManager();

        EntityTransaction tx = em.getTransaction();
        tx.begin();

        try {

            Movie movie = new Movie();
            movie.setDirector("aaaa");
            movie.setActor("bbbb");
            movie.setName("바람과함께사라지다");
            movie.setPrice(10000);

            em.persist(movie);

            em.flush();
            em.clear();

            // 이 부분
            Item item = em.find(Item.class, movie.getId());
            System.out.println("item = " + item);

            tx.commit();
        } catch (Exception e) {
            tx.rollback();
        } finally {
            em.close();
        }

        emf.close();
    }
}

이렇게 하고 실행하게 되면,

union으로 모든 테이블을 확인하게 된다. 그래서 성능상 단점을 가지게 된다.

 

 

🎈 각 전략의 장단점

🎁 각각 테이블로 변환 -> 조인 전략

  • 장점
    • 테이블이 정규화 되어 있다.
    • 외래 키 참조 무결성 제약조건 활용가능
    • 저장공간 효율화
  • 단점
    • 조회 시에 조인을 많이 사용, 성능 저하
    • 조회 쿼리가 복잡함
    • 데이터 저장 시 INSERT SQL 2번 호출 - 생각보다 단점은 아님
    • 관리되는 테이블이 많아지는 것

이 방법이 정석이라고 생각해도 된다.

 

🎁 통합 테이블로 변환 -> 단일 테이블 전략

  • 장점
    • 조인이 필요 없으므로 일반적으로 조회 성능이 빠름
    • 조회 쿼리가 단순함
  • 단점
    • 자식 엔티티가 매핑한 컬럼은 모두 null 허용해야 한다. 즉 Album에 대한 데이터를 넣을 때 Movie나 Book에 대한 데이터는 null 값을 넣게 해야 함(무결성 측면에서 애매함).
    • 단일 테이블에 모든 것을 저장하므로 테이블이 커질 수 있다. 상황에 따라서 조회 성능이 오히려 느려질 수 있다.

 

🎁 서브타입 테이블로 변환 -> 구현 클래스마다 테이블 전략

이 전략은 쓰면 안된다고 보면 된다.

  • 장점
    • 서브 타입을 명확하게 구분해서 처리할 때 효과적
    • not null 제약조건 사용 가능
  • 단점
    • 여러 자식 테이블을 함께 조회할 때 성능이 느림(UNION SQL 필요)
    • 자식 테이블을 통합해서 쿼리하기 어려움

 

728x90
반응형