2022. 7. 28. 20:47ㆍLanguage`/JPA
프로젝션 (Projection)
프로젝션이란 SELECT 절에서 조회할 대상을 지정하는 것을 의미한다
SELECT 절에서 조회할 수 있는 대상은 크게 3가지 분류로 나눌 수 있다
1. 엔티티 타입 프로젝션
엔티티 프로젝션은 말그대로 원하는 엔티티(객체) 자체를 통째로 조회하는 것이다
// Member(Entity)
List<Member> members = em.createQuery(
"SELECT m FROM Member m",
Member.class
).getResultList();
// Team(Entity : Member -> Team)
List<Team> team = em.createQuery(
"SELECT m.team FROM Member m",
Team.class
).getResultList();
Member와 Team간에 @ManyToOne 관계를 맺었기 때문에 Member는 Team을 참조하고 있고 따라서 Member → Team으로 객체 그래프 조회를 할 수 있다
>> 가장 중요한 사실은 "조회한 엔티티는 영속성 컨텍스트의 관리 대상"이라는 점이다
2. 임베디드 타입 프로젝션
임베디드 타입은 엔티티와 비슷하게 사용하긴 한데 한가지 제약이 존재한다
임베디드 타입을 조회의 시작점으로는 절대 사용하지 못한다. 반드시 엔티티를 거쳐서 사용해야 한다
이 말이 무슨 말인지는 다음 2가지 코드를 비교하면서 살펴보자
List<Address> address = em.createQuery(
"SELECT a FROM Address a",
Address.class
).getResultList();
이 코드는 From절을 보면 알겠지만 Address를 조회의 시작점으로 생성한 JPQL이다
이러한 오류가 터지는거는 당연한 일이다
왜냐하면 JPQL 자체가 "엔티티를 대상으로 쿼리"를 생성하는 것인데 임베디드 타입은 엔티티가 아니라 VO(Value Object)이기 때문에 애초에 JPQL에서 조회의 시작점으로 당연히 활용할 수 없다
따라서 Order의 Address를 조회하려면 다음과 같이 조회해야 한다
List<Address> address = em.createQuery(
"SELECT o.address FROM Order o",
Address.class
).getResultList();
3. 스칼라 타입 프로젝션
스칼라 타입은 그냥 단순히 자바의 기본 데이터 타입들을 조회한다는 것이다
// String 타입 (Member의 username)
List<String> userName = em.createQuery(
"SELECT m.username FROM Member m",
String.class
).getResultList();
// Integer 타입 (Member의 age)
List<Integer> userName = em.createQuery(
"SELECT m.age FROM Member m",
Integer.class
).getResultList();
// Double 타입 (Member의 age 평균)
Double averageAge = em.createQuery(
"SELECT avg(m.age) FROM Member m",
Double.class
).getSingleResult();
※ 여러 값(타입) 동시 조회
여러 값(타입)을 동시에 프로젝션 해야하는 상황이 오면 createQuery에서 특정 타입을 지정할 수 없다
따라서 createQuery는 TypeQuery가 아니라 Query로 만들어진다
// SELECT 절에 여러 타입 공존
List<Object[]> result = em.createQuery(
"SELECT m.username, m.age FROM Member m"
).getResultList();
// SELECT 절에 한가지 타입만 존재
List<Object> result = em.createQuery(
"SELECT m.username FROM Member m"
).getResultList();
물론 밑에 m.username을 조회하는 JPQL의 경우 "String.class"로 타입을 지정해줄 수 있다
※ DTO 변환 조회 (new 연산자)
@Data
@AllArgsConstructor
public class UserDto {
private String username;
private Integer age;
}
일단 User에 대한 조회용 UserDto 클래스를 하나 생성해두었다
@Data는 절대로 쓰지 말것을 권장하고, 위는 테스트를 위한 클래스이므로 사용
List<UserDto> result = em.createQuery(
"SELECT new JPA.QueryPractice.UserDto" +
"(m.username, m.age)" +
" FROM Member m",
UserDto.class
).getResultList();
이와 같이 new 명령어를 사용해서 직접 DTO를 대상으로 쿼리를 생성해서 날릴 수 있다
DTO 직접 조회할때는 new 뒤에 해당 DTO의 전체 Path를 다 적어줘야 한다
DTO를 직접 조회하게 되면 Entity → DTO로 따로 변환하는 과정 없이 그대로 사용할 수 있어서 편리하다
new 명령어를 사용할 때 주의해야 할 점은 다음과 같다
1. 패키지 명을 포함한 전체 클래스 명을 입력
2. 순서와 타입이 일치하는 생성자 필요
페이징 API
페이징을 처리하는 SQL을 반복적으로 작성하는것은 굉장히 지루하고 귀찮은 일이다
JPA는 페이징 처리를 위한 API를 다음 2가지로 추상화 시켰다
- setFirstResult(int startPosition)
- setMaxResults(int maxResult)
setFirstResult는 조회의 시작 위치를 정해주는 것이고 setMaxResults는 몇개의 데이터를 페이징 처리할 것인가를 나타낸 것이다
여기서 주의해야할 점은 "조회의 시작 위치는 0"이라는 것이다
위의 데이터들은 페이징 처리를 위한 테스트 데이터들이다
처음부터 1000건의 데이터 조회 (나이 오름차순 정렬)
List<Member> resultList = em.createQuery(
"SELECT m FROM Member m ORDER BY m.age",
Member.class
).setFirstResult(0)
.setMaxResults(1000)
.getResultList();
3번째부터 4개의 데이터 조회 (나이 오름차순 정렬)
List<Member> resultList = em.createQuery(
"SELECT m FROM Member m ORDER BY m.age",
Member.class
).setFirstResult(2)
.setMaxResults(4)
.getResultList();
조건은 3번째부터 조회하라고 했지만 setFirstResult의 시작 위치가 0이므로 3번째부터 조회하려면 setFirstResult(2)로 설정해야 정확히 3번째 데이터부터 조회를 할 수 있다