JPA 연관관계 매핑 - 상속관계 매핑 전략
By on October 23, 2019
상속관계 매핑 전략
이번시간 에는 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프로그래밍(저자:김영한) 도서를 학습하면서 정리 한 내용 입니다.