jeonyoungho Oct 28, 2021 2021-10-28T00:00:00+09:00
Oct 29, 2021 2021-10-29T11:08:47+09:00 7 min
본 포스팅은 인프러의 JPA 기본편을 수강하고 정리하는 내용입니다.
JPQL(Java Persistence Query Language) - 기본 문법과 기능
JQPL 소개
- JPQL은 객체지향 쿼리 언어다.따라서 테이블을 대상으로 쿼리하는 것이 아니라 엔티티 객체를 대상으로 쿼리한다.
- JPQL은 SQL을 추상화해서 특정데이터베이스 SQL에 의존하지 않는다.
- JPQL은 결국 SQL로 변환된다.
JPQL 문법
select m from Member as m where m.age > 18
- 엔티티와 속성은 대소문자 구분O (Member, age)
- JPQL 키워드는 대소문자 구분X (SELECT, FROM, where)
- 엔티티 이름 사용, 테이블 이름이 아님(Member)
- 별칭은 필수(m) (as는 생략가능)
집합과 정렬
1
2
3
4
5
6
7
| select
COUNT(m), //회원수
SUM(m.age), //나이 합
AVG(m.age), //평균 나이
MAX(m.age), //최대 나이
MIN(m.age) //최소 나이
from Member m
|
집합과 정렬
TypeQuery, Query
- TypeQuery: 반환 타입이 명확할 때 사용
- Query: 반환 타입이 명확하지 않을 때 사용
1
2
3
| TypedQuery<Member> query = em.createQuery("SELECT m FROM Member m", Member.class);
Query query = em.createQuery("SELECT m.username, m.age from Member m");
|
결과 조회 API
- query.getResultList():
결과가 하나 이상
일 때, 리스트 반환- 결과가 없으면 빈 리스트 반환, NPE걱정 안해도됨
- query.getSingleResult():
결과가 정확히 하나
, 단일 객체 반환- 결과가 없으면: javax.persistence.NoResultException
- 둘 이상이면: javax.persistence.NonUniqueResultException
- 뭔가 결과가 없는데 Exception이 나오면 굉장히 별로다.. try catch로 처리해야되고..
- 나중에 Sping Data Jpa를 사용하면 결과가 없으면 Null이나 Optional로 반환한다.
- Spring쪽 코드를 살펴보면 try catch로 잡아주는게 알아서 다 짜여있다.
- 둘의 차이를 명확하게 알아둘 것
파라미터 바인딩 - 이름 기준, 위치 기준
1
2
3
4
5
6
| SELECT m FROM Member m where m.username=:username
query.setParameter("username", usernameParam);
SELECT m FROM Member m where m.username=?1
query.setParameter(1, usernameParam);
// 이처럼 위치기반은 사용하지 말 것, 왜냐하면 중간에 뭐하나 끼어넣으면 순서가 밀려 장애로 이어진다.
|
일반적으로 체이닝을 활용한다.
1
2
3
4
| Member result = em.createQuery("select m from Member as m where m.username = :username", Member.class)
.setParameter("username", "member1")
.getSingleResult();
System.out.println("result = " + result.getUsername());
|
프로젝션
- SELECT 절에 조회할 대상을 지정하는 것
- 프로젝션 대상: 엔티티, 임베디드 타입, 스칼라 타입(숫자, 문자등 기본 데이터 타입)
- SELECT
m
FROM Member m -> 엔티티 프로젝션
1
2
3
4
5
| List<Member> result = em.createQuery("select m from Member as m", Member.class)
.getResultList();
Member findMember = result.get(0);
findMember.setAge(20);
|
엔티티 프로젝션을 하면 결과로 나온 대상들 전부 영속성 컨텍스트에서 관리된다.
- SELECT
m.team
FROM Member m -> 엔티티 프로젝션
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| List<Team> result = em.createQuery("select m.team from Member as m", Team.class)
.getResultList();
SQL:
Hibernate:
/* select
m.team
from
Member as m */ select
team1_.id as id1_3_,
team1_.name as name2_3_
from
Member member0_
inner join
Team team1_
on member0_.TEAM_ID=team1_.id
|
Member와 Team을 조인하는 쿼리가 나간다. 참고로 조인이라는게 성능에 영향을 줄 수 있는 요소가 많기에 심플하게 가줘야 한다. 위에 코드처럼 JPQL을 작성하면 쿼리를 예측하기 힘들 수 있기 때문에 아래처럼 명시적으로 나타낼 수 있도록 변경해야 한다.
1
2
| List<Team> result = em.createQuery("select t.team from Member as m join m.team t", Team.class)
.getResultList();
|
- SELECT
m.address
FROM Member m -> 임베디드 타입 프로젝션- 엔티티에 종속되있기 때문에 어디소속인지 엔티티를 정해줘야한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| List<Address> result = em.createQuery("select o.address from Order as o", Address.class)
.getResultList();
SQL:
Hibernate:
/* select
o.address
from
Order as o */ select
order0_.city as col_0_0_,
order0_.street as col_0_1_,
order0_.zipcode as col_0_2_ from
ORDERS order0_
|
- SELECT
m.username, m.age
FROM Member m -> 스칼라 타입 프로젝션- 타입이 두 개인데 어떻게 타입을 명시해야 하지? 아래 단락에서 설명해준다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| List<Address> result = em.createQuery("select m.username, m.age from Member as m")
.getResultList();
SQL:
Hibernate:
/* select
m.username,
m.age
from
Member as m */ select
member0_.username as col_0_0_,
member0_.age as col_1_0_
from
Member member0_
|
프로젝션 - 여러 값 조회
SELECT m.username, m.age FROM Member m
- 1)Query 타입으로 조회
1
2
3
4
5
6
7
| List resultList = em.createQuery("select m.username, m.age from Member as m")
.getResultList();
Object o = resultList.get(0);
Object[] result = (Object[]) o;
System.out.println("username = " + result[0]);
System.out.println("age = " + result[1]);
|
1
2
3
4
5
6
| List<Object[]> resultList = em.createQuery("select m.username, m.age from Member as m")
.getResultList();
Object[] result = resultList.get(0);
System.out.println("username = " + result[0]);
System.out.println("age = " + result[1]);
|
- 3)new 명령어로 조회
- 단순 값을 DTO로 바로 조회 ~~~ ex1. SELECT new jpabook.jpql.UserDTO(m.username, m.age) FROM Member m
ex2. public class MemberDTO {
private String username; private int age;
public MemberDTO(String username, int age) { this.username = username; this.age = age; }
… }
List resultList = em.createQuery("select new jpql.MemberDTO(m.username, m.age) from Member as m", MemberDTO.class) .getResultList();
MemberDTO memberDTO = resultList.get(0); System.out.println(“memberDTO = “ + memberDTO.getUsername()); System.out.println(“memberDTO = “ + memberDTO.getAge()); ~~~
- 문자이기 때문에 패키지 명을 포함한 전체 클래스명 입력해야되는게 단점이다. (패키지명이 길어진다면..)
- 이런 단점은 QueryDSL쓰면 극복이 된다. 패키지명을 다 import해서 쓸 수 있다.
- 순서와 타입이 일치하는 생성자 필요