고급 매핑
해당 내용은
자바 ORM 표준 JPA 프로그래밍 - 기본편 (https://www.inflearn.com/course/ORM-JPA-Basic/dashboard)
강의를 듣고 정리한 포스트입니다.
목차
• 상속관계 매핑
• @MappedSuperclass
• 실전 예제 - 4. 상속관계 매핑
상속관계 매핑
상속관계 매핑
• 관계형 데이터베이스는 상속 관계X
• 슈퍼타입 서브타입 관계라는 모델링 기법이 객체 상속과 유사
• 상속관계 매핑: 객체의 상속과 구조와 DB의 슈퍼타입 서브타입
관계를 매핑
논리모델 물리모델
공통적인 속성이 있다.
물품은 , 특징적 값은 따로 내린다.
상속관계 매핑
• 슈퍼타입 서브타입 논리 모델을 실제 물리 모델로 구현하는 방법
• 각각 테이블로 변환 -> 조인 전략
• 통합 테이블로 변환 -> 단일 테이블 전략
• 서브타입 테이블로 변환 -> 구현 클래스마다 테이블 전략
주요 어노테이션
• @Inheritance(strategy=InheritanceType.XXX)
• JOINED: 조인 전략
• SINGLE_TABLE: 단일 테이블 전략
• TABLE_PER_CLASS: 구현 클래스마다 테이블 전략
• @DiscriminatorColumn(name=“DTYPE”)
• @DiscriminatorValue(“XXX”)
조인 전략
RDB에서 어떻게 가져가냐
슈퍼 타입인 ITEM_ID를 둔다
서브 타입 테이블은 슈퍼 타입의 PK인 ITEM_ID를 , PK,FK로 둔다.
INSERT할때 2번 INSERT를 한다.
대신에 PK만으로는 이게 어떤 물품인지 알수 없으므로
구분자인 DTYPE을 둔다.
DTYPE을 통해 DTYPE이 엘범이면 ALBUM 테이블에 조인하여 가져온다.
조인 전략
• 장점
• 테이블 정규화
• 외래 키 참조 무결성 제약조건 활용가능
• 저장공간 효율화
• 단점
• 조회시 조인을 많이 사용, 성능 저하
• 조회 쿼리가 복잡함
• 데이터 저장시 INSERT SQL 2번 호출
단일 테이블 전략
• 장점
• 조인이 필요 없으므로 일반적으로 조회 성능이 빠름
• 조회 쿼리가 단순함
• 단점
• 자식 엔티티가 매핑한 컬럼은 모두 null 허용
• 단일 테이블에 모든 것을 저장하므로 테이블이 커질 수 있다. 상
황에 따라서 조회 성능이 오히려 느려질 수 있다.
구현 클래스마다 테이블 전략
구현 클래스마다 테이블 전략
• 이 전략은 데이터베이스 설계자와 ORM 전문가 둘 다 추천X
• 장점
• 서브 타입을 명확하게 구분해서 처리할 때 효과적
• not null 제약조건 사용 가능
• 단점
• 여러 자식 테이블을 함께 조회할 때 성능이 느림(UNION SQL 필요)
• 자식 테이블을 통합해서 쿼리하기 어려움
예제
@Entity public class Item { @Id @GeneratedValue private Long id; private String name; private int price; } |
@Entity public class Book extends Item{ private String author; private String isbn; } |
@Entity public class Movie extends Item{ private String director; private String actor; } |
이런식으로 했을 때
JPA는 단일 테이블 전략을 가져간다. 이런식으로 명시적으로 해도됨
SINGLE_TABLE: 단일 테이블 전략
단일 테이블 전략일경우 @DiscriminatorColumn 애너테이션이 없어도
DTYPE 컬럼이 자동생성된다.
@Entity @Inheritance(strategy = InheritanceType.SINGLE_TABLE) @DiscriminatorColumn public class Item { @Id @GeneratedValue private Long id; |
테이블생성문
create table Item ( DTYPE varchar(31) not null, id bigint not null, name varchar(255), price integer not null, atrist varchar(255), author varchar(255), isbn varchar(255), actor varchar(255), director varchar(255), primary key (id) ) Hibernate |
조인전략을 선택할 수 있다.
@Entity @Inheritance(strategy = InheritanceType.JOINED) public class Item { @Id @GeneratedValue private Long id; private String name; private int price; } |
요런식으로 할 경우 조인 전략을 가져간다
create table Album ( atrist varchar(255), id bigint not null, primary key (id) ) Hibernate: create table Book ( author varchar(255), isbn varchar(255), id bigint not null, primary key (id) ) Hibernate: create table Item ( id bigint not null, name varchar(255), price integer not null, primary key (id) ) Hibernate: create table Movie ( actor varchar(255), director varchar(255), id bigint not null, primary key (id) ) |
Movie movie = new Movie(); movie.setDirector("aaaa"); movie.setActor("bbbb"); movie.setName("아이언맨"); movie.setPrice(10000); em.persist(movie); |
insert 가 2번 나간다.
item,Movie
select 할경우 조인해서 가져온다.
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.getName() = " + findMovie.getName()); tx.commit(); |
select movie0_.id as id1_2_0_, movie0_1_.name as name2_2_0_, movie0_1_.price as price3_2_0_, movie0_.actor as actor1_5_0_, movie0_.director as director2_5_0_ from Movie movie0_ inner join Item movie0_1_ on movie0_.id=movie0_1_.id where movie0_.id=? |
근데 구분하는 DType 컬럼이 없음
Item 클래스에
@DiscriminatorColumn을 넣음
@Entity @Inheritance(strategy = InheritanceType.JOINED) @DiscriminatorColumn public class Item { |
기본 DTYPE에 들어가는 값은 Default는 Entity 명이다.
만약에 해당 들어가는 값을 바꾸고 싶으면
서브 클래스에 아래와같은 어노테이션을 붙이면된다. (개인적으루 추천은 안함 , 회사 규정일경우)
@DiscriminatorValue("M")
그러면 이런식으로 들어감
/* insert hello.jpa.Movie */ insert into Item (name, price, DTYPE, id) values (?, ?, 'M', ?) |
DTYPE은 운영상 있는게 좋다.
TABLE_PER_CLASS: 구현 클래스마다 테이블 전략
@Entity @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) public abstract class Item { @Id @GeneratedValue private Long id; |
create table Album ( id bigint not null, name varchar(255), price integer not null, atrist varchar(255), primary key (id) ) Hibernate: create table Book ( id bigint not null, name varchar(255), price integer not null, author varchar(255), isbn varchar(255), primary key (id) ) Hibernate: create table Movie ( id bigint not null, name varchar(255), price integer not null, actor varchar(255), director varchar(255), primary key (id) ) |
요런식으로 생긴다.
abstract 클래스로 상위클래스로 변환해야한다.
단일 클래스로 만들면 해당 Item 테이블 자체를 쓸 수 도 있기 때문에
@Discriminate 전략이 필요가 없다 .
단순하게 값을 넣고 뺄때는 좋다
언제 망하냐
조회를 하는데
부모클래스 타입으로 해당 객체를 가져올때
select를 union All 로 전체 테이블을 뒤져야한다.
장단점 설명
조인전략
- 데이터가 정규화 되어있음
- 조금 성능이 안나온다.
조인전략이 정석이라고 생각해야한다.
단일테이블전략
- 조회성능이 빠름
- 자식 entity 컬럼은 모두 null 허용
구현 클래스마다 테이블전략
- 쓰면 안되는 전략
- 둘다 싫어함 ,DB 설계자, ORM 전문가 도 추천안함
- 정산이 만약에 추가될 때 마다 코드를 다 고쳐야됨
- 변경이라는 관점에서 굉장히 좋지 않음
@MappedSuperclass
@MappedSuperclass
- 공통 매핑 정보가 필요할 때 사용 (id, name)
객체 입장에서 귀찮아서 공통 속성을 상속해서 사용하고 싶을 때 사용
DB는 다 따로 나눠져 있다.
만약에 DB에서 테이블에 누가 수정했는지 언제 수정했는지에 관한 데이터가 있어야한다.
(DBA가 정했다 치고)
그러면 각 객체에 관련 멤버정보를 넣어줘야한다.
그럴때 MappedSuperClass를 이용한다.
@MappedSuperclass public class BaseEntity { private String createdBy; private LocalDateTime createdDate; private String lastModifiedBy; private LocalDateTime lastModifiedDate; |
@Entity public class Member extends BaseEntity{ @Id @GeneratedValue @Column(name="MEMBER_ID") private Long id; @Column(name = "USER_NAME") private String name; |
@Entity public class Team extends BaseEntity{ @Id @GeneratedValue @Column(name = "TEAM_ID") private Long id; private String name; |
테스트를 해본다.
public static void main(String[] args) { EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello"); EntityManager em = emf.createEntityManager(); EntityTransaction tx = em.getTransaction(); tx.begin(); try { Member member = new Member(); member.setName("kim"); member.setCreatedBy("kim"); member.setCreatedDate(LocalDateTime.now()); em.persist(member); em.flush(); em.clear(); tx.commit(); |
실행시 create 되는 테이블
create table Team ( TEAM_ID bigint not null, createdBy varchar(255), createdDate timestamp, lastModifiedBy varchar(255), lastModifiedDate timestamp, name varchar(255), primary key (TEAM_ID) ) create table Member ( MEMBER_ID bigint not null, createdBy varchar(255), createdDate timestamp, lastModifiedBy varchar(255), lastModifiedDate timestamp, USER_NAME varchar(255), LOCKER_ID bigint, TEAM_ID bigint, primary key (MEMBER_ID) ) |
@MappedSuperclass
• 상속관계 매핑X
• 엔티티X, 테이블과 매핑X
• 부모 클래스를 상속 받는 자식 클래스에 매핑 정보만 제공
• 조회, 검색 불가(em.find(BaseEntity) 불가)
• 직접 생성해서 사용할 일이 없으므로 추상 클래스 권장
@MappedSuperclass public abstract class BaseEntity { |
누군가 실수 할 수 있으므로 추상클래스로 생성권장 (캡슐화와도 같다.)
@MappedSuperclass
• 테이블과 관계 없고, 단순히 엔티티가 공통으로 사용하는 매핑
정보를 모으는 역할
• 주로 등록일, 수정일, 등록자, 수정자 같은 전체 엔티티에서 공통
으로 적용하는 정보를 모을 때 사용
• 참고: @Entity 클래스는 엔티티나 @MappedSuperclass로 지
정한 클래스만 상속 가능
실전 예제 - 4. 상속관계 매핑
요구사항 추가
- 상품의 종류는 음반, 도서,영화가 있고 이후 더 확장 될 수 있다.
- 모든 데이터는 등록일과 수정일이 필수다.
도메인 모델
도메인 상세 모델
테이블 설계
위 구조에 맞춰 객체를 생성해준다.
@Entity public class Album extends Item { private String artist; private String etc; |
@Entity public class Movie extends Item{ private String director; private String actor; |
@Entity public class Book extends Item{ private String author; private String isbn; |
이 후에 Item 테이블 자체를 단독으로 사용할 일 이 있는지 고려해야한다
이 예제에서는 단독으로 사용할 일이 없다고 가정한다.
@Entity @Inheritance(strategy = InheritanceType.SINGLE_TABLE) @DiscriminatorColumn public abstract class Item { @Id @GeneratedValue @Column(name = "ITEM_ID") private Long id; private String name; private int price; private int stockQuantity; |
기억이 애매해서 복습 개념
슈퍼타입 서브타입 논리 모델 -> 물리모델 구현 방법
- 객체는 상속을 지원하므로 모델링과 구현이 똑같지만, DB는 상속을 지원하지 않으므로 논리 모델을 물리 모델로 구현할 방법이 필요하다.
- @Inheritance(strategy=InheritanceType.XXX)의 stategy를 설정해주면 된다.
- default 전략은 SINGLE_TABLE(단일 테이블 전략)이다.
- @DiscriminatorColumn(name="DTYPE")
- @DiscriminatorValue("XXX")
------------------------------------------------------------------------------------------------------------------------------
SingleTable 전략이므로 Item 테이블에 해당 값이 전부 들어감
create table Item ( DTYPE varchar(31) not null, ITEM_ID bigint not null, name varchar(255), price integer not null, stockQuantity integer not null, actor varchar(255), director varchar(255), author varchar(255), isbn varchar(255), artist varchar(255), etc varchar(255), primary key (ITEM_ID) ) |
@MappedSuperclass
적용
@MappedSuperclass public abstract class BaseEntity { private String createBy; private LocalDateTime createDate; private String lastModifiedBy; private LocalDateTime lastModifiedDate; |
후 해당 사용하는 클래스를 extends 해준다.
public abstract class Item extends BaseEntity{ |
실전에서 상속 관계를 사용하는 하느냐
그냥 싱글테이블로하고 해당 데이터를 json으로 말아넣느냐.
정답은 없다 .
객체지향적으로 가다가
장점과 단점이 트레이드 오프하는 시점에 바꿔야한다.
-------------------------------------------------------------------------------------------