1. 개요
- 프로젝션과 JPA 페이징 API에 대한 개념을 정리한다.
2. 프로젝션이란?
- SELECT 절에 조회할 대상을 지정하는 것을 말한다.
- 조회된 대상은 모두 영속성 컨텍스트에서 관리된다.
3. 조회할 대상 (프로젝션 대상)
- 조회할 대상은 엔티티, 임베디드 타입, 스칼라 타입(숫자, 문자, 등 기본 데이터 타입)이 있다. 각 타입의 의미는 예제를 통해 쉽게 이해 가능하다.
SELECT m FROM Member m // Member 엔티티를 조회하는 엔티티 프로젝션
SELEECT m.team FROM Member m // Member 엔티티와 관계를 맺고 있는 Team 엔티티를 조회하는 엔티티 프로젝션
SELECT m.address FROM Member m // Member 엔티티에 임베디드 타입인 address를 조회하는 임베디드 타입 프로젝션
SELECT m.username, m.age FROM Member m // 기본 데이터 타입들을 조회하는 스칼라 타입 프로젝션
4. 여러 값 조회하기
- 한 로우에 여러 값을 조회한다는 것은 반환 값이 명확하지 않다는 뜻이다. 예를들어 위의 스칼라 타입 프로젝션 같은 경우 username이라는 String과, age라는 int형을 조회하는데 두 값을 한번에 받을 수 있는 타입이 존재하지 않기 때문이다.
- 이처럼 반환 값이 명확하지 않을 경우 1차적으로 Query 타입으로 조회하게 된다.
- getResultList 사용 시에는 결과를 ArrayList<Object>로, getSingleResult 사용 시에는 결과를 Object 형태로 리턴받는다.
- 조회한 로우의 각 속성들을 조회하고 싶다면 Object를 Object[]로 캐스팅하여 result[0], result[1]과 같이 조회해야한다.
- 이러한 매커니즘을 활용하여 여러 값을 조회하는 방법은 크게 3가지가 있다.
1) 앞서 언급한 Query 타입으로 조회하는 방법
2) 반환 값을 Object[]로 명확히 하여 TypeQuery 타입으로 조회하는 방법
3) new 명령어로 조회하는 방법
4.1. Query 타입 조회
// Query 타입 조회 방법
Object result2 =
em.createQuery("select m.username, m.id from Member m where m.id = 1L").getSingleResult();
Object[] objects = (Object[])result2;
System.out.println(objects[0]); // username
System.out.println(objects[1]); // id
- Query 타입으로 조회 시 Object로 받게 되며, 각 속성에 접근하기 위해 반드시 Object 배열로 캐스팅해야하고 배열 번호로 접근해야 하는 단점이 있다.
4.2. TypedQuery 타입 조회(= Object[] 조회)
// TypedQuery 타입 조회 방법 == Object[] 타입 조회
Object[] result3 =
em.createQuery("select m.username, m.id from Member m where m.id = 1L",Object[].class).getSingleResult();
System.out.println(result3[0]); // username
System.out.println(result3[1]); // id
- TypedQuery 타입으로 조회 시 createQuery 메서드에서 Object[] 로 캐스팅 작업을 먼저 하기때문에 추가적인 캐스팅 코드를 작성하지 않는다. 하지만 배열 번호를 통한 접근은 그리 좋지 않아보인다.
4.3. new 생성자를 통한 조회
// new 생성자를 통한 조회
MemberDto result3 =
em.createQuery("select new jqpl.MemberDto(m.id, m.username) from Member m where m.id = 1L",MemberDto.class).getSingleResult();
System.out.println(result3.getId()); // username
System.out.println(result3.getUsername()); // id
...
// MemberDto.java
public class MemberDto {
private Long id;
private String username;
public MemberDto(Long id, String username) {
this.id = id;
this.username = username;
}
...
// getter, setter
}
- new 생성자를 사용할 경우 JPQL의 조회 형태에 맞는 생성자를 가진 DTO 클래스를 생성해야 한다.
- 배열의 번호를 통한 접근이 아니고 캐스팅 코드가 없는 깔끔한 조회 방식이나 JPQL에 DTO에 대한 풀 패키지 경로를 입력해야하는 단점이 있다. 하지만 이는 쿼리 DSL에서 극복되었으므로 이 방식을 사용하는 것이 권장된다.
5. 페이징 API
- JPA는 페이징 처리 시 setFirstResult, setMaxResults라는 메서드로 추상화한다.
setFirstResult(int startPosition) // startPosition : 조회할 시작 위치
setMaxResults(int maxResult) // maxResult : 조회할 데이터 수
- 내부적으로 동작되는 쿼리는 JPA에 설정한 Database 방언에 맞게 실행된다.
6. 페이징 API 예제
- 나이 오름차순으로 조회한 멤버 리스트들 중 0번째부터 시작해 10개의 데이터를 조회하는 예제이다. setFirstResult(0), set MaxResults(10)으로 하여 간단히 조회 가능하다.
for(int i =0 ; i< 100 ; i++){
Member member = new Member();
member.setUsername("멤버"+i);
member.setAge(i);
em.persist(member);
}
// 0번째부터 시작해서 10개의 데이터를 조회한다.
List<Member> list =
em.createQuery("select m from Member m order by m.age asc",Member.class)
.setFirstResult(0)
.setMaxResults(10)
.getResultList();
for(Member member : list){
System.out.println(member.toString());
}
//실행결과
Member{id=1, username='멤버0', age=0}
Member{id=2, username='멤버1', age=1}
Member{id=3, username='멤버2', age=2}
Member{id=4, username='멤버3', age=3}
Member{id=5, username='멤버4', age=4}
Member{id=6, username='멤버5', age=5}
Member{id=7, username='멤버6', age=6}
Member{id=8, username='멤버7', age=7}
Member{id=9, username='멤버8', age=8}
Member{id=10, username='멤버9', age=9}
7. 참고
- JAVA ORM 표준 JPA 프로그래밍 - 김영한
'백엔드 > JPA' 카테고리의 다른 글
[JPA] ConstraintViolationException 발생 원인 / 영속성 전이 (0) | 2023.05.17 |
---|---|
[JPA] JPQL 내부 조인, 외부 조인, 세타 조인 (0) | 2023.04.13 |
[JPA] JPQL 개념 및 기본 문법 (0) | 2023.04.12 |