본문 바로가기
Dev/JPA

[JPA] 고급 매핑

by dev_jsk 2021. 8. 24.
728x90
반응형

상속 관계 매핑

  • 객체의 상속 구조와 DB의 슈퍼타입 서브타입 관계를 매핑하는 것
  • 객체는 상속관계가 있지만 관계형 데이터베이스는 상속 관계가 없다.
  • 슈퍼타입 서브타입 관계라는 모델링 기법이 객체 상속과 유사하다.
  • 슈퍼타입 서브타입 논리 모델을 실제 물리 모델로 구현하는 방법
    (1) 각각 테이블로 변환 -> 조인 전략
    (2) 통합 테이블로 변환 -> 단일 테이블 전략
    (3) 서브타입 테이블로 변환 -> 구현 클래스마다 테이블 전략

주요 어노테이션

  • @Inheritance(strategy = InheritanceType.XXX)
    JOINED : 조인 전략
    SINGLE_TABLE : 단일 테이블 전략
    TABLE_PER_CLASS : 구현 클래스마다 테이블 전략
  • @DiscriminatorColumn(name = "DTYPE")
    구분자 컬럼 설정 (기본값 : DTYPE)
  • @DiscriminatorValue("XXX")
    구분자 컬럼값 설정 (기본값 : 엔티티명)

조인 전략

  • 엔티티를 각각의 테이블로 변환하여 조인 관계를 맺는 전략
  • 장점
    - 테이블 정규화가 되어있어 설계가 깔끔하여 정석으로 많이 사용된다.
    - 외래키 참조 무결성 제약조건을 활용할 수 있다.
    - 저장공간을 효율적으로 사용할 수 있다.
  • 단점
    - 조회할 때 조인을 많이 사용하여 성능이 저하될 수 있다.
    - 조회 쿼리가 복잡하다.
    - 데이터 저장 시 insert SQL이 2번 호출된다.

구조

예제

Entity

- Item.java

@Entity
@Inheritance(strategy = InheritanceType.JOINED) // 기본 전략은 SINGLE_TABLE
public abstract class Item {
    @Id @GeneratedValue
    private Long id;
    private String name;
    private int price;

    // getter, setter
}

- Album.java

@Entity
public class Album extends Item {  // 상속 관계
    private String artist;

    // getter, setter
}

- Movie.java

@Entity
public class Movie extends Item {  // 상속 관계
    private String director;
    private String actor;

    // getter, setter
}

- Book.java

@Entity
public class Book extends Item {  // 상속 관계
    private String author;
    private String isbn;

    // getter, setter
}

Main

- JpaMain.java

public class JpaMain {
    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
        EntityManager em = emf.createEntityManager();
        EntityTransaction tx = em.getTransaction();
        tx.begin();

        try {
            Movie movie = new Movie();
            movie.setDirector("AAA");
            movie.setActor("BBB");
            movie.setName("킹콩");
            movie.setPrice(8000);

            em.persist(movie);

            // 1차캐시 비우기
            em.flush();
            em.clear();

            // 조회
            Movie findMovie = em.find(Movie.class, movie.getId());
            System.out.println("findMovie = " + findMovie);
            
            tx.commit();
        } catch (Exception e) {
            tx.rollback();
        } finally {
            em.close();
        }

        emf.close();
    }
}

동작 결과

(1) Table DDL

(2) Insert SQL 및 결과

insert SQL 이 2번 실행된다.

(3) Select SQL 및 결과

조인이 발생한다.

단일 테이블 전략

  • 각각의 엔티티를 하나의 통합 테이블로 변환하여 사용하는 전략
  • 장점
    - 조인이 필요 없으므로 일반적으로 조회 성능이 빠르다.
    - 조회 쿼리가 단순하다.
  • 단점
    - 자식 엔티티가 매핑한 컬럼은 모두 NULL이 허용되어야 한다. (데이터 무결성 입장에서 애매한 부분이 있다.)
    - 단일 테이블에 모든 것을 저장하므로 테이블이 커질 수 있다. 상황에 따라서 조회 성능이 오히려 느려질 수 있다.

구조

예제

Entity

- Item.java

@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE) // 기본 전략은 SINGLE_TABLE
public abstract class Item {
    @Id @GeneratedValue
    private Long id;
    private String name;
    private int price;

    // getter, setter
}

- Album.java, Movie.java, Book.java는 이전 예제와 동일

 

Main

- JpaMain.java는 이전 예제와 동일

 

동작 결과

(1) Table DDL

(2) Insert SQL 및 결과

자식 엔티티의 매핑 값에 NULL 이 허용된다.

(3) Select SQL 및 결과

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

  • 각각의 자식 엔티티에 부모 엔티티의 정보가 포함되어 서브타입 테이블로 변환하여 사용하는 전략
  • 데이터베이스 설계자와 ORM 전문가 둘 다 추천하지 않는다.
  • 장점
    - 서브 타입을 명확하게 구분해서 처리할 때 효과적이다.
    - NOT NULL 제약조건 사용 가능
  • 단점
    - 여러 자식 테이블을 함께 조회할 때 성능이 느리다. (union SQL 필요)
    - 자식 테이블을 통합해서 쿼리하기 어렵다.

구조

예제

Entity

- Item.java

@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) // 기본 전략은 SINGLE_TABLE
public abstract class Item {
    @Id @GeneratedValue
    private Long id;
    private String name;
    private int price;

    // getter, setter
}

- Album.java, Movie.java, Book.java는 이전 예제와 동일

 

Main

- JpaMain.java

public class JpaMain {
    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
        EntityManager em = emf.createEntityManager();
        EntityTransaction tx = em.getTransaction();
        tx.begin();

        try {
            Movie movie = new Movie();
            movie.setDirector("AAA");
            movie.setActor("BBB");
            movie.setName("킹콩");
            movie.setPrice(8000);

            em.persist(movie);

            // 1차캐시 비우기
            em.flush();
            em.clear();

            // 조회(하위 클래스)
            Movie findMovie = em.find(Movie.class, movie.getId());
            System.out.println("findMovie = " + findMovie);
            // 개별테이블 전략 상위 클래스 조회 (각각의 하위 테이블 조회하여 union)
            Item item = em.find(Item.class, movie.getId());
            System.out.println("Item = " + item);
            
            tx.commit();
        } catch (Exception e) {
            tx.rollback();
        } finally {
            em.close();
        }

        emf.close();
    }
}

 

동작 결과

(1) Table SQL

(2) Insert SQL 및 결과

(3) Select SQL 및 결과 (하위 클래스 조회)

(4) Select SQL 및 결과 (상위 클래스 조회)

상위클래스 조회 시 하위 클래스에 해당하는 테이블을 전부 union 해서 조회한다.

@DiscriminatorColumn(name = "DTYPE")

  • 구분자 컬럼을 설정하는 어노테이션으로 부모 클래스에서 사용한다. 기본값은 DTYPE
  • 단일 테이블 전략은 해당 어노테이션 없이도 테이블 내에서 데이터를 구분할 수 없기 때문에 구분자 컬럼이 필수로 생성된다.

예제

- Item.java

@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) // 기본 전략은 SINGLE_TABLE
@DiscriminatorColumn    // 구분자 컬럼 지정
public abstract class Item {
    @Id @GeneratedValue
    private Long id;
    private String name;
    private int price;

    // getter, setter
}

동작 결과

기본값 DTYPE 으로 컬럼이 생성된다.

@DiscriminatorValue("XXX")

  • 부모 클래스에서 생성한 구분자 컬럼에 들어갈 값을 설정하는 어노테이션. 기본값은 엔티티명

예제

- Movie.java

@Entity
@DiscriminatorValue("M")    // 구분자 컬럼 내 값 지정(기본값: 엔티티명)
public class Movie extends Item {
    private String director;
    private String actor;

    // getter, setter
}

동작 결과

DTYPE 컬럼에 지정한 값이 저장된다.

매핑 정보 상속 (@MappedSuperclass)

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

구조

예제

공통 클래스 (@MappedSuperclass)

- BaseEntity.java

@MappedSuperclass
public abstract class BaseEntity {
    
    @Column(name = "INSERT_MEMBER")  // 매핑 컬럼 지정 가능
    private String createdBy;
    private LocalDateTime createdDate;
    @Column(name = "UPDATE_MEMBER")
    private String lastModifiedBy;
    private LocalDateTime lastModifiedDate;

    // getter, setter
}

Entity

- Item.java

@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) // 기본 전략은 SINGLE_TABLE
@DiscriminatorColumn
public abstract class Item extends BaseEntity {  // 매핑 정보 상속(BaseEntity)
    @Id @GeneratedValue
    private Long id;
    private String name;
    private int price;

    // getter, setter
}

- Movie.java

@Entity
@DiscriminatorValue("M")
public class Movie extends Item {  // 매핑 정보를 상속받은 부모 클래스를 상속 받는다.
    private String director;
    private String actor;

    // getter, setter
}

동작 결과

 

공통 매핑 정보가 추가된 것을 확인할 수 있다.

728x90
반응형

댓글