JPQL은 객체지향 쿼리 언어다.
따라서 테이블을 대상으로 쿼리를 만드는 것이 아니라 엔티티 객체를 대상으로 쿼리를 만든다.
JPQL은 SQL을 추상화해서 특정데이터베이스 SQL에 의존하지 않는다.
JPQL은 결국 SQL로 변환된다.
이번 강의에서 JPQL을 배우면서 사용할 객체와 DB 모델이다.
JPQL 문법
- 엔티티와 속성은 대소문자 구분을 한다.
- JPQL 키워드는 대소문자 구분을 하지 않는다.(SELECT, FROM, WHERE)
- 엔티티 이름을 사용한다. 테이블 이름이 아니다.
- 별칭은 필수이다 (SQL에서 as 를 통해 지칭했던 것)
집합과 정렬
select
COUNT(m), //회원수
SUM(m.age), //나이 합
AVG(m.age), //평균 나이
MAX(m.age), //최대 나이
MIN(m.age) //최소 나이
from Member m
모두 잘 나온다. SQL에서 사용했던 것을 사용할 수 있다. GROUP BY, HAVING , ORDER BY 모두 사용 가능하다.
TypeQuery, Query
- TypeQuery : 반환 타입이 명확할 때 사용한다.
- Query : 반환 타입이 명확하지 않을 때 사용한다.
//반환 타입이 Member로 명확한 경우
TypedQuery<Member> query1 = em.createQuery("select m from Member m", Member.class);
//반환 타입이 명확하지 않다. m.username은 String, m.age는 int이다
Query query2 = em.createQuery("select m.username, m.age from Member m");
결과 조회 API
- query.getResultList(): 결과가 하나 이상일 때, 리스트 반환한다. 결과가 없으면 빈 리스트를 반환한다. Null 상관안한다
- query.getSingleResult(): 결과가 정확히 하나, 단일 객체 반환한다.
결과가 없으면: javax.persistence.NoResultException, 둘 이상이면: javax.persistence.NonUniqueResultException
파라미터 바인딩 - 이름 기준, 위치 기준
//이름 기준
Member singleResult = em.createQuery("select m from Member m where m.username = :username", Member.class)
.setParameter("username", "member1")
.getSingleResult();
System.out.println("singleResult.getUsername() = " + singleResult.getUsername());
//위치 기준 사용X
Member singleResult = em.createQuery("select m from Member m where m.username = ?1", Member.class)
.setParameter(1, "member1")
.getSingleResult();
System.out.println("singleResult.getUsername() = " + singleResult.getUsername());
프로젝션
select절에 조회할 대상을 지정하는 것이다.
프로젝션 대상은 엔티티, 임베디드 타입, 스칼라 타입(숫자, 문자등 기본 데이터 타입) 등이 존재한다.
- 엔티티 프로젝션 : select m from Member m, select m.team from Member m (영속성 컨텍스트에서 관리를 한다)
- 임베디드 타입 프로젝션 : select m.address from Member m
- 스칼라 타입 프로젝션 : select m.username, m.age from Member m
- Distinct로 중복 제거 가능하다.
프로젝션으로 여러 값 조회하기
SELECT m.username, m.age FROM Member m와 같이 username과 age가 타입이 다를 경우 조회하는 방법이다
- Query 타입으로 조회하기
List resultList = em.createQuery("select m.username, m.age from Member m").getResultList();
Object o = resultList.get(0);
Object[] result = (Object[]) o;
System.out.println("username = " + result[0]);
System.out.println("age = " + result[1]);
- Object[] 타입으로 조회하기
List<Object[]> resultList = em.createQuery("select m.username, m.age from Member m")
.getResultList();
Object[] result = resultList.get(0);
System.out.println("username = " + result[0]);
System.out.println("age = " + result[1]);
- new 명령어로 조회하기
//MemberDTO 생성
public class MemberDTO {
private String username;
private int age;
..Getter/Setter
}
//Main문
List<MemberDTO> result = em.createQuery("select new jpql.MemberDTO(m.username, m.age) from Member m").getResultList();
MemberDTO memberDTO = result.get(0);
System.out.println("username = " + memberDTO.getUsername());
System.out.println("age = " + memberDTO.getAge());
패키지 명을 포함한 전체 클래스 명을 입력해야한다. 순서와 타입이 일치하는 생성자가 필요하다.
페이징 API
페이징 API는 인스타그램이나 배달의민족에서 한 화면에 10개 씩 정보를 보여주고 밑으로 내리면 10개를 더 보여주는 API이다.
JPA는 페이징을 다음 두 API로 추상화한다.
- setFirstResult(int startPosition) : 조회 시작 위치(0부터 시작)
- setMaxResults(int maxResult) : 조회할 데이터
List<Member> result = em.createQuery("select m from Member m order by m.age desc", Member.class)
.setFirstResult(1) //시작 위치(offset)
.setMaxResults(10) //몇개씩 보여줄지
.getResultList();
조인
- 내부 조인 : SELECT m FROM Member m [INNER] JOIN m.team t, Team에 Member와 연관된 것이 없으면 출력 안된다.
- 외부 조인 : SELECT m FROM Member m LEFT [OUTER] JOIN m.team t, Member와 연관된 Team이 없더라도 Team이 모두 NULL로 출력된다.
- 세타 조인 : select count(m) from Member m, Team t where m.username = t.name, Member와 Team을 모두 조인 하고 이름이 같은 것을 모두 출력한다. 연관관계가 없는 것을 모두 조사해볼 때 사용한다.
조인 - ON절
- 조인 대상 필터링, 예) 회원과 팀을 조인하면서 팀이름이 A인 팀만 조인
SELECT m, t FROM Member m LEFT JOIN m.team t on t.name = 'A'
- 연관관계 없는 엔티티 외부 조인, 예) 회원의 이름과 팀의 이름이 같은 대상 외부 조인
SELECT m, t FROM Member m LEFT JOIN Team t on m.username = t.name
서브 쿼리
- 예) 나이가 평균보다 많은 회원
select m from Member m
where m.age > (select avg(m2.age) from Member m2)
//m2와 m은 다른 Member 매인 쿼리와 서브 쿼리를 관계가 없게 해야한다.
- 예) 한 건이라도 주문한 고객
select m from Member m
where (select count(o) from Order o where m = o.member) > 0
//매인 쿼리를 끌고 와야할 때도 있다
서브 쿼리 지원 함수
- [NOT] EXIST (subquery) : 서브쿼리에 결과가 존재하면 참이다. 이때, 몇개 조건을 만족해야 하는지도 정할 수 있다. ALL은 모두 만족하면 참이다, ANY,SOME은 같은 의미, 조건을 하나라도 만족하면 참이다.
- [NOT] IN (subquery) : 서브쿼리의 결과 중 하나라도 같은 것이 있으면 참이다.
예제
- 팀A 소속인 회원
select m from Member m
where exists (select t from m.team t where t.name = ‘팀A')
- 전체 상품 각각의 재고보다 주문량이 많은 주문들
select o from Order o
where o.orderAmount > ALL (select p.stockAmount from Product p)
- 어떤 팀이든 팀에 소속된 회원
select m from Member m
where m.team = ANY (select t from Team t)
JPA 서브 쿼리 한계
- JPA는 WHERE, HAVING 절에서만 서브 쿼리를 사용 가능하다.
- SELECT 절도 가능 하다.(하이버네이트에서 지원 한다)
- FROM 절의 서브 쿼리는 현재 JPQL에서 불가능 하다. -> 조인으로 풀 수 있으면 풀어서 해결한다.
JPQL 타입 표현
- 문자 : ‘HELLO’, ‘She’’s’
- 숫자: 10L(Long), 10D(Double), 10F(Float)
- Boolean: TRUE, FALSE
- ENUM: jpabook.MemberType.Admin (패키지명 포함)
String query = "select m.username from Member m "+
"where m.type =: userType";
List<Member> result = em.createQuery(query, Member.class)
.setParameter("userType", ADMIN)
.getResultList();
- 엔티티 타입: TYPE(m) = Member (상속 관계에서 사용)
- 표준 SQL 모두 지원
조건식 - CASE 식
기본 CASE 식
String query =
"select " +
"case when m.age <= 10 then '학생요금' " +
" when m.age >= 60 then '경로요금' " +
" else '일반요금' "+
"end " +
"from Member m";
List<String> result = em.createQuery(query, String.class)
.getResultList();
for (String s : result) {
System.out.println("s = " + s);
}
단순 CASE 식
select
case t.name
when '팀A' then '인센티브110%'
when '팀B' then '인센티브120%'
else '인센티브105%'
end
from Team t
COALESCE
하나씩 조회해서 null이 아니면 반환한다.
String query =
"select coalesce(m.username, '이름 없는회원') from Member m";
List<String> result = em.createQuery(query, String.class)
.getResultList();
for (String s : result) {
System.out.println("s = " + s);
}
NULLIF
두 값이 같으면 null 반환, 다르면 첫번째 값을 반환 한다.
String query =
"select nullif(m.username, '관리자') from Member m";
List<String> result = em.createQuery(query, String.class)
.getResultList();
for (String s : result) {
System.out.println("s = " + s);
}
JPQL 함수
JPQL 기본 함수
- CONCAT : 두 문자열을 합친다. (select concat('a' || 'b') From Member m 이것도 가능하다 하이버네이트 지원)
- SUBSTRING : 부분 문자열 구하기
- TRIM : 공백 제거
- LOWER, UPPER : 대소문자 바꾸기
- LENGTH : 문자열 길이
- LOCATE : select locate('dc', 'abcdefg') from Member m을 할 경우 4를 반환한다. 발견된 위치 리턴
- ABS, SQRT, MOD
- SIZE, INDEX : size는 컬렉션의 크기를 리턴
사용자 정의 함수 호출
하이버네이트는 사용전 방언에 추가해야 한다.
사용하는 DB 방언을 상속받고, 사용자 정의 함수를 등록한다.
사용하는 법
1. diarect 패키지를 만든다.
2. diarect 패키지 안에 MyH2Dialect를 추가한다.
package dialect;
import org.hibernate.dialect.H2Dialect;
import org.hibernate.dialect.function.StandardSQLFunction;
import org.hibernate.type.StandardBasicTypes;
public class MyH2Dialect extends H2Dialect {
public H2Dialect(){
registerFunction("group_concat", new StandardSQLFunction("group_concat", StandardBasicTypes.STRING));
}
}
3. persistence.xml에 아래 코드를 추가한다.
<property name="hibernate.dialect" value="dialect.MyH2Dialect"/>
4. 사용한다.
String query =
"select function('group_concat', m.username) from Member m";
List<String> result = em.createQuery(query, String.class)
.getResultList();
for (String s : result) {
System.out.println("s = " + s);
}
두 출력이 떨어지지 않고 붙어서 나왔다.
강의 출처 : https://www.inflearn.com/course/ORM-JPA-Basic
'Spring JPA 공부 > 자바 ORM 표준 JPA 프로그래밍' 카테고리의 다른 글
JPA - 중급 문법과 기능 (0) | 2023.01.01 |
---|---|
JPA - 객체지향 쿼리 언어(JPQL) 소개 (0) | 2022.12.30 |
실전 예제 6 - 값 타입 매핑 (0) | 2022.12.29 |
JPA - 값 타입 (0) | 2022.12.29 |
실전 예제 5 - 연관관계 관계 (0) | 2022.12.28 |