고급매핑

twocowsong·2023년 4월 28일
0

김영한_jpa

목록 보기
8/13
post-thumbnail

상속관계 매핑

관계형 데이터베이스는 상속 관계는 없지만 관계형 DB에서 슈퍼타입 서브타입 관계라는 모델링 기법이 객체 상속과 유사합니다.
객체의 상속과 구조와 DB의 슈퍼타입 서브타입 관계를 매핑할수있습니다.

슈퍼타입 서비타입 논리 모델을 실제 물리모델로 구현하는 방법은 아래와 같은 방법들이 있습니다.
• 각각 테이블로 변환 -> 조인 전략
• 통합 테이블로 변환 -> 단일 테이블 전략
• 서브타입 테이블로 변환 -> 구현 클래스마다 테이블 전략

조인전략


정규화가 진행된 ITEM 테이블에서만 NAME과 PRICE가 들어가게 됩니다.
ALBUM, MOVIE, BOOK에만 각각 다른 데이터들이 들어가게됩니다.
가장 깔끔하게 정규화된 상태입니다.

@Entity
@Inheritance(strategy = InheritanceType.JOINED) // 조인전략
@Setter
@Getter
public class Item {
	@Id @GeneratedValue
	private Long id;
	private String name;
	private int price;
}
@Entity
@Setter
@Getter
public class Album extends Item{
	private String artist;

}
@Entity
@Setter
@Getter
public class Movie extends Item{
	private String director;
	private String actor;

}
@Entity
@Getter
@Setter
public class Book extends Item{
	private String author;
	private String isbn;

}
Movie movie = new Movie();
movie.setDirector("홍길동");
movie.setActor("김태희");
movie.setName("바람과함께사라지다");
movie.setPrice(10000);
em.persist(movie);

조인전략으로 INSERT시 아래와같은 결과를 얻을수 있습니다.

Movie findMovie = em.find(Movie.class, movie.getId());
System.out.println("findMove NAME : " + findMovie.getName());
System.out.println("findMove ACTOR : " + findMovie.getActor());

당연히 위처럼 조회시 아래와같은 결과도 한번의 find로 모두 조회가 가능하게됩니다.


	...
    @Inheritance(strategy = InheritanceType.JOINED) // 조인전략
	@DiscriminatorColumn
	...

Item객체에서 @DiscriminatorColumn추가 시 아래처럼 DTYPE 컬럼이 추가됩니다.

추후 운영시, DTYPE컬럼 하나로 어디서 사용되는지를 판단할수있게됩니다.
@DiscriminatorColumn(name = "DIS_TYPE") 이렇게 컬럼 이름도 변경이 가능합니다.

자식 엔티티에서는 @DiscriminatorValue(value = "M") 어노테이션으로 값을 아래처럼 변경도 가능합니다.

단일 테이블 전략


시스템이 작을경우, 테이블 1개에 모든데이터를 넣어서 사용이 가능합니다.

@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE) // 조인전략
@DiscriminatorColumn(name = "DIS_TYPE")
@Setter
@Getter
public class Item {
	@Id @GeneratedValue
	private Long id;
	private String name;
	private int price;
}
@Entity
@Setter
@Getter
@DiscriminatorValue(value = "I")
public class Album extends Item{
	private String artist;
}
@Entity
@Getter
@Setter
@DiscriminatorValue(value = "B")
public class Book extends Item{
	private String author;
	private String isbn;
}
@Entity
@Setter
@Getter
@DiscriminatorValue(value = "M")
public class Movie extends Item{
	private String director;
	private String actor;
}

InheritanceType.SINGLE_TABLE 값으로만 바꿔주시면 단일 테이블 전략입니다.

    Movie movie = new Movie();
	movie.setDirector("홍길동");
	movie.setActor("김태희");
	movie.setName("바람과함께사라지다");
	movie.setPrice(10000);
	em.persist(movie);


다른 테이블없이, Item테이블만 생성된걸 확인할수있습니다.
데이터도 다른 Album, Book정보는 모두 null로 들어간걸 확인할수있습니다.

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


Item클래스를 없애고 Item에있던 값들이 각각에 클래스에 추가되는 형태입니다.

@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) // 조인전략
@DiscriminatorColumn(name = "DIS_TYPE")
@Setter
@Getter
public abstract class Item {
	@Id @GeneratedValue
	private Long id;
	private String name;
	private int price;
}

Item클래스에 abstract class로 변경 후 Inheritance.strategy값을 InheritanceType.TABLE_PER_CLASS로만 바꾸면됩니다.

Hibernate: 
    
    create table Album (
       id bigint not null,
        name varchar(255),
        price integer not null,
        artist 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)
    )

item테이블은 생성되지않고 movie테이블에 아래처럼 데이터가 들어간것을 확인할수있습니다.

여기서, 테이블전략의 가장 큰문제점은
아래처럼 find를 하여 부모클래스를 찾을경우 모든 테이블을 조회하게됩니다.

Item item = em.find(Item.class, movie.getId());
Hibernate: 
    select
        item0_.id as id1_5_0_,
        item0_.name as name2_5_0_,
        item0_.price as price3_5_0_,
        item0_.author as author1_1_0_,
        item0_.isbn as isbn2_1_0_,
        item0_.artist as artist1_0_0_,
        item0_.actor as actor1_11_0_,
        item0_.director as director2_11_0_,
        item0_.clazz_ as clazz_0_ 
    from
        ( select
            id,
            name,
            price,
            author,
            isbn,
            null as artist,
            null as actor,
            null as director,
            1 as clazz_ 
        from
            Book 
        union
        all select
            id,
            name,
            price,
            null as author,
            null as isbn,
            artist,
            null as actor,
            null as director,
            2 as clazz_ 
        from
            Album 
        union
        all select
            id,
            name,
            price,
            null as author,
            null as isbn,
            null as artist,
            actor,
            director,
            3 as clazz_ 
        from
            Movie 
    ) item0_ 
where
    item0_.id=?

각각의 전략 장단점

조인전략 장점

  • 외래 키 참조 무결성 제약조건이 활용 가능
    -> 다른테이블에서 ITEM PK를 볼때 편리, 다른테이블에서 ITEM을 볼때 설계가 깔끔해짐
  • 테이블의 정규화
  • 저장공간 효율화
  • 대체로 객체랑 잘맞고, 설계입장에서 깔끔하기 때문에 기본으로 많이 사용

조인전략 단점

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

단일 테이블 전략 장점

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

단일 테이블 전략 단점

  • 자식 엔티티가 매핑한 컬럼은 모두 null 허용
    -> 사용하지않는 컬럼이 확장성에따라 점점 늘어남
  • 단일 테이블에 모든 것을 저장하므로 테이블이 커지고, 상황에 따라서 조회 성능이 오히려 느려질 수 있음(임계점을 넘어야지 발생하는 상황)

구현 클래스마다 테이블 전략 장점

  • 이 전략은 데이터베이스 설계자와 ORM 전문가 둘 다 추천X
  • 서브 타입을 명확하게 구분해서 처리할 때 효과적
  • not null 제약조건 사용 가능

구현 클래스마다 테이블 전략 단점

  • 여러 자식 테이블을 함께 조회할 때 성능이 느림(UNION SQL 필요)
  • 자식 테이블을 통합해서 쿼리하기 어려움

Mapped Superclass - 매핑 정보 상속


공통 매핑 정보가 필요할 때 사용합니다.
예를들어 모든 테이블에 누가, 언제 입력했는지 정보를 입력해줘야한는 상황일경우 아래와같이 클래스를 추가해줍니다.

@Getter
@Setter
@MappedSuperclass
public abstract class BaseEntity {
	private String createBy;
	private LocalDateTime createDate;
	private String lastModifyedBy;
	private LocalDateTime lastModifiedDate;
}

그리고 사용할 엔티티에서 상속받아서 사용하시면 됩니다.

Member member = new Member();
member.setUsername("user1");
member.setCreateBy("kim");
member.setCreateDate(LocalDateTime.now());
em.persist(member);


엔티티가 아니기때문에 테이블과 매핑은 없습니다.
테이블과 관계 없고, 단순히 엔티티가 공통으로 사용하는 매핑 정보를 모으는 역할이며 주로 등록일, 수정일, 등록자, 수정자 같은 전체 엔티티에서 공통 으로 적용하는 정보를 모을 때 사용합니다.
참고!) @Entity 클래스는 엔티티나 @MappedSuperclass로 지정한 클래스만 상속 가능합니다.

profile
생각하는 개발자

0개의 댓글