본문 바로가기
Back-end/JPA 개념

24. JPQL - 프로젝션(Projection, Select)

by devraphy 2022. 4. 22.

0. 개요

- 이번 포스팅에서는 JPQL을 이용하여 Select 절을 작성하는 방법에 대해서 알아보자.

 

 

1. 프로젝션(= projection)

- 프로젝션이란 JPQL의 Select 절을 이용하여 어떤 데이터를 조회할 것인가를 말한다.

 

- Select를 절의 조회 대상을 무엇으로 작성하냐에 따라 프로젝션의 종류가 달라진다.

 

- 프로젝션은 조회하는 데이터의 속성에 따라서 다음 3가지 종류로 구분된다.

    → Entity(객체) 프로젝션

    → Embedded 프로젝션

    → 스칼라 프로젝션 

 

 

2. Entity 프로젝션

a) Entity 프로젝션이란?

- 객체 또는 객체의 속성을 조회하는 Select문을 말한다.

 

b) 기본 사용방법

- 다음 예시를 보면서 이해해보자.

 

- 첫 번째 예시는 객체를 조회하는 프로젝션이다.

- Entity 프로젝션은 조회하는 객체의 클래스가 매개변수로 들어간다.(= Typed Query)

em.createQuery("select p from Player p", Player.class);

 

 

- 두 번째 예시는 객체가 가진 속성을 조회하는 프로젝션이다.

- 여기서 주의해야 할 것은 team은 값 타입이 아니라 Entity라는 것이다.

- 다만, Team은 Entity로 존재하기 때문에 Team 클래스가 매개변수로 들어간다.

em.createQuery("select p.team from Player p", Team.class);

 

3. Embedded 타입 프로젝션

a) Embedded 타입 프로젝션이란?

- Entity의 속성 중 값 타입으로 생성된 속성을 조회하는 Select문을 말한다.

 

b) 기본 사용 방법

- Embedded 프로젝션의 사용 방법은 Entity 프로젝션을 사용하는 방법과 동일하다.

 

- 다음 예시를 살펴보자.

-조회하는 데이터가 값 타입이므로 값 타입 클래스가 매개변수로 들어간다.(= Typed Query)

em.createQuery("select p.address from Player p", Address.class);

 

 

4. 스칼라 타입 프로젝션 

a) 스칼라 타입 프로젝션이란?

- 스칼라 타입 프로젝션은 조회하는 데이터의 타입과 상관없이 여러 개를 가져오는 방법이다.

 

 

b) 기본 사용 방법

- 다음 예시를 살펴보자.

em.createQuery("select p.name, p.age, p.team from Player p");

 

- 위의 코드에서 조회하는 데이터의 속성은 모두 다르다. 

    → name은 String, age는 int, team은 Entity

 

- 그러므로 createQuery() 메서드에 조회하는 데이터의 클래스가 매개변수로 들어가지 않는다. (= Query)

- 스칼라 타입 프로젝션은 조회되는 데이터의 자료형을 규정할 수 없으므로 Query 형식을 사용한다. 

 

 

c) Query 

- 이전 포스팅에서 TypedQuery와 Query에 대해서 알아보았다.

- 스칼라 타입 프로젝션은 서로 다른 자료형을 가진 데이터를 조회하므로, Query를 사용한다.

 

- Query 형식은 조회 결과의 자료형을 규정할 수 없으므로 Object 배열을 사용하여 받아야 한다.

- 다음 예시 코드를 살펴보자.

List<Object[]> resultList = 
               em.createQuery("select p.name, p.address, p.team from Player p")
               .getResultList();
               
Object[] result = resultList.get(0);

System.out.println("result = " + result[0]);
System.out.println("result = " + result[1]);
System.out.println("result = " + result[2]);

 

- 다음 사진은 실행 결과다.

 

- 위의 코드처럼, 스칼라 타입 프로젝션의 경우 Object 배열을 사용하여 데이터를 받아온다.

- 그러나 이 방식은 선호되는 방식은 아니다. 그러므로 DTO와 new 키워드를 사용한 방법을 알아보자. 

 

 

d) DTO와 new 키워드를 사용한 방법

- 위에서 Object 배열을 사용한 방법은 코드가 깔끔하지 않다. 

- 더불어, 데이터를 추출하는 과정도 깔끔하다고 할 수 없다. 

- 그러므로 DTO와 new 키워드를 사용하여 데이터를 받아올 수 있다.

 

- DTO는 스칼라 타입 프로젝션의 조회 결과를 담는, 일종의 자료형(= 클래스)처럼 작동하는 것이다.

 

- 예를 들어, 위에서 사용한 예시처럼 name(= String), address(= 값 타입), team(= Entity)을 조회하려고 한다.

- 그렇다면 다음과 같이 DTO를 형성할 수 있다.

public class PlayerDTO {

    private String name;
    private Address address;
    private Team team;
    
    public PlayerDTO(String name, Address address, Team team) {
       this.name = name;
       this.address = address;
       this.team = team;
    }
    
    // Getter & Setter 생략
}

 

- 그다음에는 new jpql.DTO를 사용하여 다음과 같이 JPQL을 작성한다.

List<PlayerDTO> resultList =
                    em.createQuery("select new jpql.PlayerDTO(p.name, p.address, p.team) from Player p", PlayerDTO.class)
                    .getResultList();

PlayerDTO playerDTO = resultList.get(0);
System.out.println("playerDTO.getName() = " + playerDTO.getName());
System.out.println("playerDTO.getAddress() = " + playerDTO.getAddress());
System.out.println("playerDTO.getTeam() = " + playerDTO.getTeam());

 

 

- 이처럼 DTO와 new 연산자를 사용하여 데이터를 받아올 수 있다.

- 그러나 매번 데이터를 받아오기 위해서 DTO를 작성할 수는 없다. 

- 그러므로 해당 데이터를 자주 조회하는 경우에는 DTO를 이용한 방식을 사용한다. 

 

 

5. 프로젝션 사용 시 주의사항 

a) 조회 결과는 영속성 Context에 의해 관리된다.

- 프로젝션으로 조회된 결과는 영속성 Context에 의해 관리된다.

- 다음 예시를 보면서 이해해보자. 

 

- DB에 다음과 같은 데이터가 저장되어 있다.

 

- 그리고 다음 코드를 실행하면 어떻게 될지 생각해보자. 

List<Player> result = em.createQuery("select p from Player p", Player.class)
                      .getResultList();
                       
Player findPlayer = result.get(0);

findPlayer.setName("Messi");
findPlayer.setPosition("CAM");

 

- 위의 예시를 보면 Entity 프로젝션으로 가져온 값 중 하나를 setName() 메서드를 이용하여 값을 변경하였다.

- 위의 코드를 실행하면 해당 객체의 name 속성은 "Messi"로, position 속성은 "CAM"으로 변경된다.

- 이처럼 조회된 값의 update가 가능한 이유는, 조회된 결과가 영속성 Context에 의해 관리되기 때문이다. 

 

- 코드의 실행 결과를 DB에서 확인해보자.

 

 

b) Join을 사용할 때에는 반드시 Join을 명시하자.

- 다음 예시 코드를 살펴보자.

List<Team> resultList = em.createQuery("select p.team from Player p", Team.class)
                        .getResultList();

 

- 위의 코드를 실행하면 다음과 같은 쿼리문이 발생한다.

 

- 이처럼 inner join이 발생한 것을 확인할 수 있다.

- 어떻게 생각해보면 굉장히 편한 기능이라고 생각할 수 있다.

- 개발자가 굳이 join을 명시하지 않아도 JPA가 알아서 join을 사용하여 데이터를 조회하기 때문이다. 

- 그러나 유지보수의 측면에서 볼 때, JPQL만 읽고 JPA가 어떤 쿼리를 실행하는지 정확히 알 수 없다.

- 그러므로 Join이 사용되면 반드시 JPQL에도 직접 Join을 명시하는 것을 권장한다. 

 

- 이 규칙을 따르면 다음과 같이 JPQL을 작성할 수 있다.

List<Team> resultList = 
             em.createQuery("select t from Player p join p.team t", Team.class)
             .getResultList();

 

- 새로 작성된 JPQL을 실행하더라도 동일한 쿼리문이 발행된다.

- 다만, JPA가 실행할 쿼리를 정확히 명시하여 개발자의 이해를 돕는다는 것에 의미와 목적이 있다. 

댓글