JPA 연관관계 매핑 - 상속관계 매핑 전략


상속관계 매핑 전략

이번시간 에는 JPA 연관관계 매핑의 상속관계 매핑 전략 대해서 정리 해보도록 하겠습니다.

  • @Inheritance : JPA 연관관계 매핑의 상속 관계에서 전략을 지정하는 어노테이션입니다..

    • @Inheritance 전략 옵션 정보
    Enum TYPE 전략 정보
    InheritanceType.JOINED 조인전략 자식 테이블이 부모의 키를 받아서 외래키로 사용하는 전략
    InheritanceType.SINGLE_TABLE 단일테이블전략 단일 테이블에 부모 자식 데이터가 전부 저장 되어있는 전략
    InheritanceType.TABLE_PER_CLASS 테이블 전략 테이블 별로 분리 되어있는 전략

조인 전략(JOINED)

조인전략 구조
조인전략 테이블

  • 조인 전략은 기본적인 키 조인 형태로 정규화된 모델링에서 사용 되는 전략 입니다.
    부모와 자식간에 키로 조인 되어있기 때문에 무결성이 보장 되지만 조인을 많이 사용 하게 되면 복잡해지고 성능이 저하 될 수 있는 단점이 있습니다.

  • 엔티티 클래스

      @Entity
      @Table(name="Shop")
      @Inheritance(strategy = InheritanceType.JOINED)
      @DiscriminatorColumn(name = "MENU")
      public abstract class Shop {
          @Id
          @GeneratedValue
          private int seq;
          private int price;
          public int getPrice() {
              return price;
          }
          public void setPrice(int price) {
              this.price = price;
          }
      }
        
      @Entity
      @DiscriminatorValue("Food")
      public class Food extends Shop{
      	private String food_name;
        
      	public String getFood_name() {
      		return food_name;
      	}
      	public void setFood_name(String food_name) {
      		this.food_name = food_name;
      	}
      }
        
      @Entity
      @DiscriminatorValue("Drink")
      public class Drink extends Shop{
      	private String drink_name;
        
      	public String getDrink_name() {
      		return drink_name;
      	}
      	public void setDrink_name(String drink_name) {
      		this.drink_name = drink_name;
      	}
      }
    
    
    • @Inheritance : 어떤 전략을 사용 할지 설정 하는 어노테이션 입니다. 조인 전략을 사용 하기 때문에 JOIN 설정이 되었습니다.
    • @DiscriminatorColumn(name = “MENU”) : 자식 테이블을 구분하기 위한 컬럼을 지정 하는 어노테이션입니다.
    • @DiscriminatorValue(“Food”) : 엔티티를 저장 할때 구분 컬럼에 입력할 값을 지정 하는 어노테이션입니다.
  • Main 클래스

      public class Main {
          public static void main(String[] args) {
              //엔티티 매니저 팩토리 생성 
              EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpaExam");
              //엔티티 매니저 생성
              EntityManager em = emf.createEntityManager();
              //엔티티 트렌젝션 생성
              EntityTransaction tx = em.getTransaction();
              try {
                  //트랜잭션 시작
                  tx.begin(); 
        
                  Food food = new Food();
                  food.setFood_name("참치 샌드위치");
                  food.setPrice(12000);
                  em.persist(food);
                    
                  Drink drink = new Drink();
                  drink.setDrink_name("맥주");
                  drink.setPrice(4500);
                  em.persist(drink);
                    
                  tx.commit();
              } catch (Exception e) {
                  e.printStackTrace();
                  tx.rollback(); //트랜잭션 롤백
              } finally {
                  em.close(); //엔티티 매니저 종료
              }
              emf.close(); //엔티티 매니저 팩토리 종료
          }
      }
    
  • 참고 : JPA의 동작을 확인 하기 위해 persistence.xml 에서 hibernate.hbm2ddl.auto 옵션을 설정 하였습니다.

      <property name="hibernate.hbm2ddl.auto" value="create" />
    

    ※ 해당 옵션은 테이블을 Drop 하고 자동으로 생성 하기 때문에 절때 조심해야 합니다. 운영테이터를 한순간에 날려 먹을 수 있습니다.

  • JPA 테이블 생성 수행 로그를 살펴 보도록 하겠습니다.

     Hibernate: 
         create table Drink (
             drink_name varchar(255),
             seq integer not null,
             primary key (seq)
         )
     Hibernate: 
         create table Food (
             food_name varchar(255),
             seq integer not null,
             primary key (seq)
         )
     Hibernate: 
         create table Shop (
             MENU varchar(31) not null,
             seq integer not null,
             price integer not null,
             primary key (seq)
         )
     Hibernate: 
         alter table Drink 
             add constraint FK_hbanfccyl19ty1a8y1scnnak5 
             foreign key (seq) 
             references Shop
     Hibernate: 
         alter table Food 
             add constraint FK_n64ymxq7m4wfc9xiwqbxatumu 
             foreign key (seq) 
             references Shop
    
    • SHOP 테이블에서는 자식 타입을 구분 할 수 있는 MENU 컬럼이 생성 되었고 Drink, Food 테이블에는 외래키로 SHOP 의 키를 갖습니다.
  • 데이터 입력 처리 로그

      Hibernate: 
          /* insert com.jpa.exam.JPA_Exam3.Food
              */ insert 
              into
                  Shop
                  (price, MENU, seq) 
              values
                  (?, 'Food', ?)
      Hibernate: 
          /* insert com.jpa.exam.JPA_Exam3.Food
              */ insert 
              into
                  Food
                  (food_name, seq) 
              values
                  (?, ?)
      Hibernate: 
          /* insert com.jpa.exam.JPA_Exam3.Drink
              */ insert 
              into
                  Shop
                  (price, MENU, seq) 
              values
                  (?, 'Drink', ?)
      Hibernate: 
          /* insert com.jpa.exam.JPA_Exam3.Drink
              */ insert 
              into
                  Drink
                  (drink_name, seq) 
              values
                  (?, ?)
    
    • Shop 테이블에 우선 insert 가 수행 되고 해당되는 자식 테이블에 데이터가 insert 된 것을 확인 할 수 있습니다.
  • 데이터 조회

      private static void findShop(EntityManager em) {
          Shop shop = em.find(Shop.class, 1);
      }
    
      Hibernate: 
          select
              shop0_.seq as seq2_2_0_,
              shop0_.price as price3_2_0_,
              shop0_1_.food_name as food_nam1_1_0_,
              shop0_2_.drink_name as drink_na1_0_0_,
              shop0_.MENU as MENU1_2_0_ 
          from
              Shop shop0_ 
          left outer join
              Food shop0_1_ 
                  on shop0_.seq=shop0_1_.seq 
          left outer join
              Drink shop0_2_ 
                  on shop0_.seq=shop0_2_.seq 
          where
              shop0_.seq=?
    
    • shop 테이블과 각각의 자식 테이블이 키로 조인되어 조회 되는것을 확인 할 수 있습니다.

단일 테이블 전략 (SINGLE_TABLE)

단일 테이블 전략 구조
단일 테이블

  • 단일 테이블 전략은 말그대로 단일 테이블에 부모 자식이 있는 경우 입니다. 단일 테이블은 조인 전략 예제 소스에서 전략 옵션만 아래와 같이 변경 하면 됩니다.

      //@Inheritance(strategy = InheritanceType.JOINED) 
      @Inheritance(strategy = InheritanceType.SINGLE_TABLE)
    
  • 단일 테이블 전략 수행 로그

      Hibernate: 
          create table Shop (
              MENU varchar(31) not null,
              seq integer not null,
              price integer not null,
              food_name varchar(255),
              drink_name varchar(255),
              primary key (seq)
          )
    
  • 단일 테이블이 생성 되어있고 food와 drink 가 추가 되어있습니다.

      Hibernate: 
          /* insert com.jpa.exam.JPA_Exam3.Food
              */ insert 
              into
                  Shop
                  (price, food_name, MENU, seq) 
              values
                  (?, ?, 'Food', ?)
      Hibernate: 
          /* insert com.jpa.exam.JPA_Exam3.Drink
              */ insert 
              into
                  Shop
                  (price, drink_name, MENU, seq) 
              values
                  (?, ?, 'Drink', ?)
    
  • 단일 테이블 전략 데이터 조회

    단일 테이블 전략 데이터
    단일 테이블

  • 단일 테이블 전략은 조인이 없기 때문에 속도가 빠른 장점이 있지만 컬럼에 NULL 을 허용 해야 하고 컬럼이 많아지는 단점이 있습니다.

클래스별 테이블 전략(TABLE_PER_CLASS)

테이블 전략 데이터

  • 테이블 전략은 엔티티 마다 각자의 테이블을 만드는 전략이다. 테이블을 조회할 때 UNION 을 사용 해서 조회 해야 하므로 조회가 느린 단점이 있기 때문에 추천하지 않는 전략이다. 변경은 아래와 같이 옵션만 변경 하면 됩니다.

    //@Inheritance(strategy = InheritanceType.JOINED) 
    @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
    
  • 테이블 전략 수행 로그

      Hibernate: 
          create table Drink (
              seq integer not null,
              price integer not null,
              drink_name varchar(255),
              primary key (seq)
          )
      Hibernate: 
          create table Food (
              seq integer not null,
              price integer not null,
              food_name varchar(255),
              primary key (seq)
          )
    
    • 각자의 테이블이 생성된 것을 볼수 있습니다.
  • 테이블 전략 데이터 입력 수행 로그

      Hibernate: 
          /* insert com.jpa.exam.JPA_Exam3.Food
              */ insert 
              into
                  Food
                  (price, food_name, seq) 
              values
                  (?, ?, ?)
      Hibernate: 
          /* insert com.jpa.exam.JPA_Exam3.Drink
              */ insert 
              into
                  Drink
                  (price, drink_name, seq) 
              values
                  (?, ?, ?)
    
    • 각자의 테이블이 생성 되었습니다.
  • 테이블 전략 조회

      Hibernate: 
          select
              shop0_.seq as seq1_2_0_,
              shop0_.price as price2_2_0_,
              shop0_.food_name as food_nam1_1_0_,
              shop0_.drink_name as drink_na1_0_0_,
              shop0_.clazz_ as clazz_0_ 
          from
              ( select
                  seq,
                  price,
                  food_name,
                  null as drink_name,
                  1 as clazz_ 
              from
                  Food 
              union
              all select
                  seq,
                  price,
                  null as food_name,
                  drink_name,
                  2 as clazz_ 
              from
                  Drink 
          ) shop0_ 
      where
          shop0_.seq=?
    
    • 로그에서 확인 할 수 있듯이 테이블을 union으로 조회 하기 때문에 성능이 느리고 자식들을 관리 하기가 어렵기 때문에 추천 하지 않는 전략입니다.

@MappedSuperclass

  • @MappedSuperclass 는 부모클래스를 상속 받는 자식 클래스에 매핑 정보만 제공하는 어노테이션입니다.

  • 부모 클래스

      @MappedSuperclass
      public abstract class Shop {
          @Id
          @GeneratedValue
          private int seq;
          private int price;
          public int getPrice() {
              return price;
          }
          public void setPrice(int price) {
              this.price = price;
          }
      }
    
    • 부모 클래스에 @Entity 대신 @MappedSuperclass 어노테이션으로 수정 합니다.
  • 자식 클래스

      @Entity
      @AttributeOverride(name = "seq", column = @Column(name="FOOD_SEQ"))
      public class Food extends Shop{
          private String food_name;
          public String getFood_name() {
              return food_name;
          }
          public void setFood_name(String food_name) {
              this.food_name = food_name;
          }
      }
      
      @Entity
      @AttributeOverrides({
      	@AttributeOverride(name = "seq", column = @Column(name="DRINK_SEQ")),
      	@AttributeOverride(name = "price", column = @Column(name="DRINK_PRICE"))
      })
      public class Drink extends Shop{
      	private String drink_name;
      	public String getDrink_name() {
      		return drink_name;
      	}
      	public void setDrink_name(String drink_name) {
      		this.drink_name = drink_name;
      	}
      }
    
    • @AttributeOverride : 부모 엔티티에서 상속 받은 필드의 이름을 재정의 할때 사용됩니다.
    • @AttributeOverrides : 어노테이션을 이용해서 여러개의 필드 이름을 재정의 할 수 있습니다.
  • 테이블 생성 수행 로그

      Hibernate: 
          create table Drink (
              DRINK_SEQ integer not null,
              DRINK_PRICE integer,
              drink_name varchar(255),
              primary key (DRINK_SEQ)
          )
      Hibernate: 
          create table Food (
              FOOD_SEQ integer not null,
              price integer not null,
              food_name varchar(255),
              primary key (FOOD_SEQ)
          )
    
    • 부모테이블과 매핑되지 않은 테이블이 생성 되었고 부모 클래스의 필드를 상속 받았습니다.
  • 데이터 입력 수행 로그

      Hibernate: 
          /* insert com.jpa.exam.JPA_Exam3.Food
              */ insert 
              into
                  Food
                  (price, food_name, FOOD_SEQ) 
              values
                  (?, ?, ?)
      Hibernate: 
          /* insert com.jpa.exam.JPA_Exam3.Drink
              */ insert 
              into
                  Drink
                  (DRINK_PRICE, drink_name, DRINK_SEQ) 
              values
                  (?, ?, ?)
    

마무리

JPA 연관관계 매핑의 상속 전략에 대해서 정리 해보았습니다. 다음에는 식별관계에 대해서 정리 해보겠습니다.

자바 ORM 표준 JPA프로그래밍(저자:김영한) 도서를 학습하면서 정리 한 내용 입니다.

Back to blog