BackEnd/JPA

[JPA] 고급 매핑

샤아이인 2022. 4. 5.

내가 공부한것을 올리며, 중요한 단원은 저 자신도 곱씹어 볼겸 상세히 기록하고 얕은부분들은 가겹게 포스팅 하겠습니다.

 

1. 상속관계 매핑

관계형 데이터베이스 같은 경우 상속 관계가 없다. 대신 슈퍼타입, 서브타입 관계라는 모델링 기법이 객체 상속과 유사한 방식이다.

 

상속관계 매핑 : 객체의 상속 구조를 DB에서의 슈퍼타입, 서브타임 관계 매핑으로 해결한다.

출처 - 인프런 김영한 JPA

 

위 그림에서 왼쪽과 같은 논리 모델을, 오른쪽과 같은 실제 물리 모델로 구현하는 방법에는 3가지가 있다.

1) 조인 전략 (각각 테이블로 변환)

2) 단일 테이블 전략 (통합 테이블로 변환)

3) 구현 클래스마다 테이블 전략 (서브타입 테이블로 변환)

 

다음과 같이 @Inheritance 를 엔티티에 추가해주면 된다.

@Inheritance(strategy = InheritanceType.JOINED)
 

참고로 @Inheritance를 지정하지 않을경우 디폴트로 JOINED가 적용된다.

 

1 - 1) 조인 전략

출처 - 인프런 김영한 JPA

이를 코드로 구현하면 다음과 같다.

 

- ITEM

@Entity
@Inheritance(strategy = InheritanceType.JOINED)
public class Item {

    @Id
    @GeneratedValue
    private Long id;

    private String name;
    private int price;
    // 생략
}
 

- ALBUM, MOVIE, BOOK

@Entity
public class Album extends Item{
    private String artist;
}

@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, Book, Movie, Album 테이블이 원하던 방식으로 생성되었다.

 

Main을 다음과 같이 만들어 실행해 보자.

Movie movie = new Movie();
movie.setDirector("브라이언 드팔마");
movie.setActor("알 파치노");
movie.setName("스카페이스");
movie.setPrice(10000);

entityManager.persist(movie);

transaction.commit();
 

movie 객체를 하나 만든 후, 영속화 시키고 있다.

결과는 다음과 같다.

Item의 PK가 Album에 FK이다. 다만 Album 에서의 FK는 스스로의 PK역할도 한다.

따라서 각각(Item, Album)의 테이블에 insert를 수행하고 Item내에 타입을 구분하는 컬럼을 만들어서 구해온다.

 

DB에서 결과를 SELECT 해보면 다음과 같다.

하지만 이 방식은 1차 캐시에서 찾아서 오기때문에 DB에서 찾아오는 쿼리를 보여주진 않는다.

따라서 코드에서 flush, clear를 추가하여 DB와 동기화를 한 후에 다시 찾아와 보자.

Movie movie = new Movie();
movie.setDirector("브라이언 드팔마");
movie.setActor("알 파치노");
movie.setName("스카페이스");
movie.setPrice(10000);
entityManager.persist(movie);

entityManager.flush(); // 동기화
entityManager.clear();

Movie findMovie = entityManager.find(Movie.class, movie.getId());
System.out.println("findMovie = " + findMovie);

transaction.commit();
 

실행 결과는 다음과 같다.

DB에서 select로 찾아올때는 inner join을 통해서 찾아오는것을 확인할수 있었다.

 

- DTYPE

생각해보면 위 다이어그램에서 ITEM에 DTYPE이 있는데 이를 빼고 구현했었다. 이번에는 DTYPE을 추가해 보자.

@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn
public class Item {
...
}
 

위와 같이 @DiscriminatorColumn 을 추가해주면 된다.

결과는 다음과 같다. ITEM에 DTYPE에 Movie가 추가되는것을 확인할수가 있다.

만약 DTYPE이 없다면 Select 를 할때, 해당 쿼리가 어떤 타입인지 알기가 어렵다.

 

DTYPE에서 보여지는 명칭이 Movie, Album, Book 이 아닌, M, A, B 처럼 보이고 싶다면 @DiscriminatorValue(“XXX”) 를 사용하면 된다.

 

- 장점

정규화도 되어있고, 외래 키 참조 무결성 제약조건을 부모에 걸어 맞출 수 있다

=> ex: Order 테이블에서 특정 아이템(영화)의 가격을 볼때 ITEM 테이블만 봐도 된다.

 

또한 정규화 되어있으니, 중복 데이터가 줄어들고 이는 저장공간 효율을 높인다.

 

- 단점

조회시 조인이 많을 경우 성능 저하가 될수있다. (큰 문제는 아니다)

조회 쿼리가 복잡함.

데이터 저장시 INSERT SQL 2번 호출(큰 문제는 아님)

 

1 - 2) 단일 테이블 전략

이 방식은 논리모델을 하나의 테이블로 합쳐버리는 방법이다.

한 테이블에 다 넣어 놓고 어떤 테이블인지 구분하는 컬럼(ex:DTYPE)을 통해 구분한다.

출처 - 인프런 김영한 JPA

전략을 SINGLE_TABLE로 변경해주면 된다.

@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn
public class Item {

    @Id
    @GeneratedValue
    private Long id;

    private String name;
    private int price;
    // 생략
}
 

위와같이 변경후 테이블을 생성하면 다음과 같다.

한 테이블 안에 전부 생성되고, Movie, Album 같은 개별 테이블은 생성되지 않는다.

이제 DB에서 값을 확인해 보자.

위와 같은 단일 테이블 전략은 INSERT 문도 쿼리가 간단하고, SELECT 할때도 JOIN을 하지 않기 때문에 쿼리가 간단하다.

@DiscriminatorColumn 이 필수적이기 때문에 생략해도 자동으로 생성된다.

 

- 장점

조인이 필요 없으므로 일반적으로 조회 성능이 빠르다. 또한 조회 쿼리가 단순하다.

 

- 단점

자식 엔티티가 매핑한 컬럼은 모두 null을 허용한다.

단일 테이블에 모든 것을 저장하므로 테이블이 커질 수 있다. 상 황에 따라서 조회 성능이 오히려 느려질 수 있다.

(하지만 일반적으로 이정도 까지 테이블이 커지기도 힘들다)

 

1 - 3) 구현 클래스마다 테이블 전략 (사용하지 말것)

이 전략은 각각의 테이블 마다 별개로 만들어 따로 관리한다.

ITEM 테이블을 생성하지 않고 ALBUM, MOVIE, BOOK 테이블에서 각각 id, name, price필드를 가지고 있다.

출처 - 인프런 김영한 JPA

개별 테이블이 만들어지기 때문에 @DiscriminatorColumn 을 사용해서 구분할 필요가 없다.

또한 Item class 자체는 테이블이 생성될 필요가 없기 때문에 abstract class로 선언해 준다.

@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class Item { // 추상클레스로 변경

    @Id
    @GeneratedValue
    private Long id;

    private String name;
    private int price;
    // 생략
}
 

실행해보면 생성되는 테이블 쿼리는 다음과 같다. 따라서 데이터를 삽입하기에는 편하다.

조회할때를 생각해 보자. 객체지향적으로 부모 클레스인 Item으로 조회를 시도할수가 있다.

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

ITEM을 조회하면 ALBUM, MOVIE, BOOK 세개의 테이블을 UNION ALL로 전부 조회해서 가져오게 된다.

명확하게 ALBUM을 찝어서 SELECT 할때는 상관없지만, 그 외의 경우에는 테이블을 전부 다 뒤져야 한다...

 

- 장점

서브 타입을 명확하게 구분해서 처리할 때 효과적이며 not null 제약조건이 사용 가능하다.

 

- 단점

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

 

2. Mapped Superclass - 매핑 정보 상속

공통 매핑 정보가 필요할때 사용한다. 무슨 의미인지 살펴보자!

 

예를 들어 Member, Seller, ...등등, 마다 공통적으로 사용되는 id, name이라는 필드가 있다고 해보자.

이를 Member, Seller, .. 에 똑같이 전부 필드를 복붙 해서 추가해주기는 불편하다.

 

따라서 공통된 필드를 BaseEntity 라는 abstract class로 만들어 이를 상속하도록 하는것 이다. 다음 그림을 살펴보자.

출처 - 인프런 김영한 JPA

 

@MappedSuperclass는 테이블과 관계 없고, 단순히 엔티티가 공통으로 사용하는 매핑 정보를 모으는 역할을 한다.

이 과정은 상속관계 매핑이 아니기 때문이다.

또한 Entity가 아니기 때문에 테이블도 생성되지 않는다.

부모 클래스를 상속 받는 자식 클래스에 매핑 정보만 제공해줄 뿐이다.

@MappedSuperclass
public abstract class BaseEntity {
    private String createdBy;
    private LocalDateTime createDate;
    private String lastModifiedBy;
    private LocalDateTime lastModifiedDate;
}
 

다음 코드와 같이 Base 타입으로 조회, 검색이 불가능 하다.

em.find(BaseEntity.class, xxx);
 

또한 직접 객체를 생성해서 사용할 일이 없으므로 추상 클래스 권장

 

(참고. @Entity 클래스는 엔티티나 @MappedSuperclass로 지 정한 클래스만 상속 가능하다)

'BackEnd > JPA' 카테고리의 다른 글

[JPA] 영속성 전이와 고아 객체  (0) 2022.04.06
[JPA] 프록시와 연관관계 관리  (0) 2022.04.06
[JPA] 다양한 연관관계 매핑  (0) 2022.04.04
[JPA] 연관관계 매핑 기초  (0) 2022.04.03
[JPA] 엔티티 매핑  (0) 2022.04.02

댓글