본문 바로가기
Framework and Tool/JPA

JPA - 객체지향 쿼리 언어 - JPQL 다형성 쿼리

by ocwokocw 2021. 8. 3.

- 참조: 자바 ORM 표준 JPA 프로그래밍

- 다형성 쿼리

앞에서 JPA 상속관계 맵핑을 배운적이 있다. Item 엔티티를 확장하여 Book, Album, Movie 엔티티를 선언하였다. Single Table 전략으로 선언했던 Item 과 Book 엔티티의 코드를 다시 한번 살펴보자.

 

@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "ITEM_TYPE")
public abstract class Item extends DateMarkable {

	@Id
	@GeneratedValue
	@Column(name = "ITEM_ID")
	private Long id;
	
	private String name;
	private int price;
	private int stockQuantity;

............

@Entity
@DiscriminatorValue("BOOK")
public class Book extends Item{

	private String author;
	private String isbn;

 

만약 위와 같이 상속 관계 맵핑을 한 상태에서 JPQL 로 Item 엔티티를 조회하면 어떻게 될까?

 

Book book = new Book();

book.setName("Book#1");
book.setAuthor("Author#1");
em.persist(book);

Movie movie = new Movie();

movie.setName("Movie#1");
movie.setDirector("Director#1");
em.persist(movie);

 

Book 과 Movie 엔티티를 선언하여 영속화한 후 Item 엔티티를 조회하는 아래 JPQL 을 수행시켜보자.

 

String jpql = "select i "
		+ "from Item i ";

List<Item> items = em.createQuery(jpql, Item.class)
	.getResultList();


items.forEach(item -> {
	
	System.out.println("Item name: " + item.getClass().getSimpleName());
});

 

위의 JPQL 을 실행하면 변환되는 SQL 과 결과는 아래와 같다. Item 엔티티를 상속받는 자식의 엔티티들을 모두 조회하는것을 알 수 있다.

 

Hibernate: 
    /* select
        i 
    from
        Item i  */ select
            item0_.item_id as item_id2_3_,
            item0_.insert_datetime as insert_d3_3_,
            item0_.update_datetime as update_d4_3_,
            item0_.name as name5_3_,
            item0_.price as price6_3_,
            item0_.stock_quantity as stock_qu7_3_,
            item0_.artist as artist8_3_,
            item0_.author as author9_3_,
            item0_.isbn as isbn10_3_,
            item0_.actor as actor11_3_,
            item0_.director as directo12_3_,
            item0_.item_type as item_typ1_3_ 
        from
            item item0_
Item name: Book
Item name: Movie

 

만약 전략을 아래와 같이 JOINED 로 변경하면 수행되는 SQL 도 달라진다.

 

@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn(name = "ITEM_TYPE")
public abstract class Item extends DateMarkable {

 

실행되는 SQL 을 보면 join 으로 변경된다.

 

Hibernate: 
    /* select
        i 
    from
        Item i  */ select
            item0_.item_id as item_id2_5_,
            item0_.insert_datetime as insert_d3_5_,
            item0_.update_datetime as update_d4_5_,
            item0_.name as name5_5_,
            item0_.price as price6_5_,
            item0_.stock_quantity as stock_qu7_5_,
            item0_1_.artist as artist1_0_,
            item0_2_.author as author1_1_,
            item0_2_.isbn as isbn2_1_,
            item0_3_.actor as actor1_7_,
            item0_3_.director as director2_7_,
            item0_.item_type as item_typ1_5_ 
        from
            item item0_ 
        left outer join
            album item0_1_ 
                on item0_.item_id=item0_1_.item_id 
        left outer join
            book item0_2_ 
                on item0_.item_id=item0_2_.item_id 
        left outer join
            movie item0_3_ 
                on item0_.item_id=item0_3_.item_id
Item name: Book
Item name: Movie

- TYPE

특정 Type 의 엔티티만 조회하고 싶다면 type() 함수를 사용할 수 있다. 

 

String jpql = "select i "
		+ "from Item i "
		+ "where type(i) in (Book)";

List<Item> items = em.createQuery(jpql, Item.class)
	.getResultList();

items.forEach(item -> {
	
	System.out.println("Item name: " + item.getClass().getSimpleName());
});

 

Item 엔티티중에 Book 엔티티만 조회하고 싶다면 type([alias]) 로 비교할 엔티티 이름을 기술해준다.

 

Hibernate: 
    /* select
        i 
    from
        Item i 
    where
        type(i) in (
            Book
        ) */ select
            item0_.item_id as item_id2_5_,
            item0_.insert_datetime as insert_d3_5_,
            item0_.update_datetime as update_d4_5_,
            item0_.name as name5_5_,
            item0_.price as price6_5_,
            item0_.stock_quantity as stock_qu7_5_,
            item0_1_.artist as artist1_0_,
            item0_2_.author as author1_1_,
            item0_2_.isbn as isbn2_1_,
            item0_3_.actor as actor1_7_,
            item0_3_.director as director2_7_,
            item0_.item_type as item_typ1_5_ 
        from
            item item0_ 
        left outer join
            album item0_1_ 
                on item0_.item_id=item0_1_.item_id 
        left outer join
            book item0_2_ 
                on item0_.item_id=item0_2_.item_id 
        left outer join
            movie item0_3_ 
                on item0_.item_id=item0_3_.item_id 
        where
            item0_.item_type in (
                'BOOK'
            )
Item name: Book

 

실행되는 SQL 을 보면 Item 엔티티에서 @DiscriminatorColumn 로 지정한 ITEM_TYPE 컬럼에 Book 엔티티에서 지정한 @DiscriminatorValue 값인 BOOK 으로 조회하는것을 확인할 수 있다.


- TREAT

TYPE 으로 특정 엔티티를 조회하는것을 넘어서 특정 엔티티의 속성으로 조회조건을 주고 싶다면 treat() 을 사용한다.

 

String jpql = "select i "
		+ "from Item i "
		+ "where treat(i as Book).author = 'Author#1'";

List<Item> items = em.createQuery(jpql, Item.class)
	.getResultList();

items.forEach(item -> {
	
	System.out.println("Item name: " + item.getClass().getSimpleName());
});

 

위의 JPQL 은 마치 타입 캐스팅처럼 Item 엔티티의 alias 인 i 를 treat(i as Book) 문법을 사용하여 Book 엔티티로 처럼 변환하였다. Book 엔티티에만 있는 author 속성의 값이 'Author#1' 인 Book 엔티티만 조회한다.

 

Hibernate: 
    /* select
        i 
    from
        Item i 
    where
        treat(i as Book).author = 'Author#1' */ select
            item0_.item_id as item_id2_5_,
            item0_.insert_datetime as insert_d3_5_,
            item0_.update_datetime as update_d4_5_,
            item0_.name as name5_5_,
            item0_.price as price6_5_,
            item0_.stock_quantity as stock_qu7_5_,
            item0_1_.artist as artist1_0_,
            item0_2_.author as author1_1_,
            item0_2_.isbn as isbn2_1_,
            item0_3_.actor as actor1_7_,
            item0_3_.director as director2_7_,
            item0_.item_type as item_typ1_5_ 
        from
            item item0_ 
        left outer join
            album item0_1_ 
                on item0_.item_id=item0_1_.item_id 
        inner join
            book item0_2_ 
                on item0_.item_id=item0_2_.item_id 
        left outer join
            movie item0_3_ 
                on item0_.item_id=item0_3_.item_id 
        where
            item0_2_.author='Author#1'
Item name: Book

 

실행된 SQL 을 보면 book 테이블만 inner join 으로 되어있다. 이는 treat 을 이용하면 해당 엔티티만 조회하기 때문이다. 만약 싱글테이블 전략으로 조회했다면 i.ITEM_TYPE = 'BOOK' 과 같은 조건이 where 절에 추가된다.

댓글