본문 바로가기
Back-end/java spring

[JPA] 고급 매핑

by kkkdh 2023. 2. 6.
728x90

상속관계 매핑

  • 객체는 상속관계가 있지만, 관계형 데이터베이스는 상속관계 X
  • 슈퍼타입, 서브타입 관계라는 모델링 기법이 그나마 객체 상속과 유사하다
  • 상속관계 매핑: 객체의 상속 구조와 DB의 슈퍼타입 서브타입 관계를 매핑하는 것

DB 논리 모델과 객체 다이어그램

데이터베이스 논리 모델을 실제 물리 모델로 구현하는 방법에는 3가지가 있다.

  • 조인 전략: 각각 테이블로 변환
  • 단일 테이블 전략: 통합 테이블로 변환
  • 구현 클래스마다 테이블 전략: 서브타입 테이블로 변환

객체는 상속관계를 지원하기 때문에, 그냥 매핑하면 된다.

 

결론은 DB에서 세가지 중 어떤 방식으로 물리 모델을 구현하더라도, JPA를 이용하면 모두 객체와 매핑이 가능하도록 지원한다는 점이다.

 

조인 전략

정규화된 테이블을 사용하는 방식

위의 다이어그램은 조인 전략을 사용하는 경우이고, 각각의 테이블에 중복되는 정보가 들어가지 않도록 잘 정규화된 형태로 테이블을 나누는 방식으로 물리 모델을 설계하는 방법이다.

 

기본적으로 상속 관계로 표현된 객체들이 DB 테이블로 매핑되는 방식은 싱글 테이블 전략을 따른다.

그래서 다음과 같이 Entity들을 설계하면, single table 전략이 반영된 Item table이 만들어지는 결과를 확인할 수 있었다.

부모 클래스가 되는 Item entity 객체
Item 클래스를 상속받은 세 개의 자식 클래스들

그냥 실행해보면

hibernate가 이런 테이블을 만드는 create문을 날린다.

일단 이걸 조인 전략으로 바꾸기 위해서는 부모 엔티티의 클래스에 @Inheritance 어노테이션의 strategy 속성을 변경해줘야 한다. 

@Inheritance(strategy = Inheritance.JOINED)

조인 전략으로 변경!
개별  테이블이 생성되며, 생성된 이후에 FK 제약 조건이 추가되는 결과 또한 확인할 수 있었다.

이렇게 조인 전략을 기반으로한 상속 관계의 표현에 대해 구현해볼 수 있었다.

 

이번에는 데이터를 추가해보자.

실행할 예제 코드
예제 코드 실행 결과 두 개의 insert 문이 날라간다.

두 개의 insert문이 날라간 결과 테이블을 확인해 보면, 다음과 같이 item table과 movie table에 입력한 영화 엔티티 객체의 정보가 입력된 결과를 확인할 수 있었다.

앞선 예제에서 다음과 같이 EntityManager flush를 호출해 쓰기 지연 SQL 저장소에 있는 sql들을 처리하고, clear method를 호출해서 EntityManager를 초기화한 뒤에 find method를 이용해서 입력한 movie 객체를 조회하는 과정을 테스트해 봤다.

그 결과 item, movie 두 개의 table을 join해서 결과를 반환하는 실행 과정을 확인할 수 있었다.

join query를 실행한 결과를 반환해 준다.

추가로 부모 클래스(엔티티)에 @DiscriminatorColumn 어노테이션을 사용하면, DB의 슈퍼 타입에 해당하는 테이블에 Dtype이라는 이름으로(default) 자식 엔티티의 종류를 기입하는 속성이 추가된다.

 

없어도 동작에는 지장이 없으나, 운영 상황이나 DB에서만 작업하는 환경을 생각했을때를 위해서 넣어주는 것이 권장된다고 한다. (name 속성으로 컬럼 명은 변경할 수 있다. 그러나 그냥 관례대로 쓰자 웬만하면)

 

또한 자식 클래스에서 @DiscriminatorValue 어노테이션을 이용해서 구분을 위한 칼럼에 들어가는 값을 자식 엔티티에 따라 따로 지정해줄 수 있다.

 

싱글 테이블 전략

두번째 단일 테이블 전략은 복잡한게 싫은 경우 하나의 테이블로 관리하는 방식이다. 따라서 각 서브타입에 해당하는 테이블에 따로 들어갈 속성이 하나의 테이블에 다 포함되며, Dtype 칼럼을 이용해서 각 row(tuple)의 데이터의 유형을 구분하게 된다.

슈퍼타입 서브타입 논리 모델이 싱글 테이블 전략으로 물리 모델에 설계되는 경우
구현은 이렇게 전략을 바꿔주면 끝이다. 혹은 기본값이 단일 테이블 전략이므로 생략하면 될 것 같다.
단일 테이블을 생성하는 sql문이 날라간다.

단일 테이블 전략으로 데이터를 추가하는 상황에서는 insert 문이 한 번만 날라간다.

조인 전략에서는 두 개의 sql문으로 이루어지던 작업

기존의 조인 전략에서는 @DiscriminatorColumn 어노테이션이 있어야 Dtype 칼럼이 추가되었는데, 단일 테이블 전략에서는 필수로 포함되어야 하는 속성이다 보니 위의 어노테이션이 없어도 기본적으로 DTYPE 칼럼이 생성된다.

조회도 조인 쿼리 없이 가능

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

조인 전략과 유사하기는 한데, 슈퍼 타입에 해당하는 Item 테이블을 없애고 서브 타입에 해당하는 테이블에 중복된 속성을 추가해서 관리하는 전략이다.

@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)

위와 같이 전략을 설정하면, 구현 클래스마다 테이블 전략이 적용된다.

 

이렇듯 전략을 변경할 때, Inheritance 어노테이션의 속성만 변경해도 된다는 점이 JPA 사용의 큰 장점이라고 볼 수 있다!!

바꿔준다.

참고로 이때에는 Item 테이블이 생성될 필요가 없기 때문에, 독단적으로 사용되는 경우를 막기위해서 Item 클래스를 추상 클래스로 변경해야 한다고 함

 

이렇게 되면, Item 테이블을 생성하지 않고, 서브 타입에 해당하는 테이블에 기존의 Item 테이블에 포함되던 공통 정보들이 중복되어 포함되서 각자 관리하도록 설계된다.

id, name, price 속성이 각 테이블에서 개별로 관리되는 구조
movie 테이블에 모든 정보가 들어간다.
추가, 조회 쿼리가 단순하다

이 전략에서는 Discriminator column을 넣어도 의미가 없어서 생성되지 않는다.

부모 타입으로 조회하는 경우 쿼리가 너무 길어서 짤린다...

단순하게 값을 넣고 뺄때는 좋은 전략인데, 조회를 부모 클래스 타입(Item)으로 조회하는 경우 개별 테이블에서 identifier를 관리하다 보니 union을 활용해서 각 테이블을 모두 뒤져야되는 불상사가 벌어진다.

 

각 전략의 장단점 정리

조인 전략

  • 장점
    • 테이블 정규화
    • 외래 키 참조 무결성 제약 조건을 활용 가능하다.
    • 저장 공간이 효율화된다. (정규화 덕분)
  • 단점
    • 조회시에 join을 많이 사용한다. (성능 저하 요인)
    • 그런 이유로 조회 쿼리가 복잡하다.
    • 데이터 저장시 insert sql이 2번 호출

성능적 측면에서의 단점이 생각보다는 크지 않으나, 관리의 측면에서 단일 테이블보다 복잡하다는 것이 오히려 단점으로 꼽힌다고 한다.

 

기본적으로는 조인 전략이 정석으로 사용된다고 함 (객체와 적합하고, 정규화되며, 설계가 깔끔하다는 장점)

 

단일 테이블 전략

  • 장점
    • join 필요없다. 그래서 조회 성능이 빠르다.
    • 조회 쿼리가 매우 단순
  • 단점
    • 자식 엔티티를 매핑한 칼럼은 모두 nullable (치명적인 단점)
    • 단일 테이블에 모든 것을 저장하므로 테이블이 커질 수 있어, 상황에 따라 조회 성능이 오히려 느려질 수 있다고 한다. (그래도 일반적으로는 성능이 더 빠르다.)

 

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

  • 결론부터 말하면, 이 전략은 사용하면 안된다고 한다.
  • 이 전략은 데이터베이스 설계자와 ORM 전문가 모두가 추천하지 않는 방식이라고 함
  • 장점
    • 서브 타입을 명확하게 구분해서 처리할 때 효과적이다. (insert, select 상황에서는 효과적일 수 있다.)
    • not null 제약 조건 사용이 가능하다 (단일 테이블 전략과 비교했을 때)
  • 단점
    • 여러 자식 테이블을 함께 조회할 때, 성능이 매우 느리다. (union sql을 사용해서)
    • 자식 테이블을 통합해서 쿼리하기가 어렵다.

상속 관계 매핑을 위해 사용하는 주요 어노테이션 정리

  • @Inheritance(strategy = InheritanceType.XXX)
    • JOINED: 조인 전략 (기본적으로 사용되는 전략이라고 생각하자)
    • SINGLE_TABLE: 단일 테이블 전략
    • TABLE_PER_CLASS: 구현 클래스마다 테이블 전략 (이 전략은 고민상에 두지 말아라)
  • @DiscriminatorColumn(name="DTYPE")
  • @DiscriminatorValue("XXX")

@MappedSuperclass

사실 상속관계 매핑이랑은 별 상관이 없다.

 

DB 테이블은 따로 생성이 되는데, 여러 객체에서 공통으로 등장하는 속성을 매번 선언하기 귀찮은 상황에서 사용하는 기법이다. 

이 상황에서는 id, name이라는 공통된 필드를 상속으로 표현해서 중복된 선언을 피하고자 하는 예제이다.

공통 매핑 정보가 필요할 때 사용한다. (id, name)

 

예시로 확인해 보자.

 

상황 가정: 우리가 설계하는 모든 테이블에 DBA가 등록자와 등록일, 수정자와 수정일 필드를 추가하도록 요청했다.

모든 테이블에 4개의 필드를 다 추가해야 한다...

이러한 상황에서 속성만 상속받아서 사용하기 위해 다음과 같은 과정을 거친다.

공통 속성들을 갖는 BaseEntity를 하나 만들고 (MappedSuperclass 어노테이션을 기재해줘야 한다.)

필요한 엔티티가 상속받도록 설계한다.

 

이때, 상속할 클래스에는 @MappedSuperclass 어노테이션을 붙여줘야만 한다.

이렇게 공통적으로 Column 어노테이션을 활용해 칼럼의 이름을 지정하는 등의 작업 또한 가능하다.

  • 상속관계 매핑이 아니다!!
  • 엔티티 X, 테이블과 매핑되지 않는다.
  • 부모 클래스를 상속 받는 자식 클래스에 매핑 정보만을 제공한다!!
  • 조회, 검색이 불가능하다(em.find(BaseEntity) 불가능)
  • 직접 생성해서 사용할 일이 없는 클래스이므로, 추상 클래스로 설계하는 것을 권장한다.
  • 테이블과 관계 없고, 단순히 엔티티가 공통으로 사용하는 매핑 정보를 모으는 역할을 한다.
  • 주로 등록일, 수정일, 등록자, 수정자 같은 전체 엔티티에서 공통으로 적용하는 정보를 모을때 사용한다.
참고: @Entity 클래스는 엔티티나 @MappedSuperclass로 지정한 클래스만 상속 가능하다.
  • @Entity 클래스 상속: 상속관계 매핑
  • @MappedSuperclass 클래스 상속: 공통 속성을 매핑하기 위해 사용

 

따라서 @MappedSuperclass는 상속관계 매핑과는 상관없이 중복된 속성들을 간단하게 매핑하기 위해 사용하는 기술이라고 볼 수 있다.

 

728x90

'Back-end > java spring' 카테고리의 다른 글

[JPA] 값 타입  (1) 2023.02.18
[JPA] 프록시  (0) 2023.02.15
[JPA] 다양한 연관관계 매핑  (0) 2023.02.01
[JPA] 연관관계 매핑 기초  (0) 2023.01.30
[JPA] 엔티티 매핑  (0) 2023.01.27

댓글