출처
https://www.inflearn.com/course/ORM-JPA-Basic/dashboard
목차
JPQL 소개
- JPQL은 객체 지향 쿼리 언어입니다. 즉, 테이블 대상이 아닌 엔티티를 대상으로 쿼리를 날립니다.
- JPQL은 결국 SQL로 변환됩니다.
- JPQL은 SQL을 추상화했기 때문에 특정 데이터베이스에 의존하지 않습니다.
JPQL 기본
select_문 :: =
select_절
from_절
[where_절]
[groupby_절]
[having_절]
[orderby_절]
update_문 :: = update_절 [where_절]
delete_문 :: = delete_절 [where_절]
[예시]
select m from Member as m where m.age > 18
- SQL과 유사합니다.
- 엔티티와 속성은 대소문자를 구분합니다. (ex) Member, int age)
- JPQL 키워드는 대소문자를 구분하지 않습니다. (ex) Select, FROM, where)
- 테이블이 아닌 엔티티 이름을 사용합니다. (ex) Member)
- 별칭은 필수입니다. (ex) fMember as m)
- as 키워드는 생략이 가능합니다. (ex) Member m)
JPQL와 SQL
- SQL과 문법이 거의 같습니다. 아래의 문법을 그대로 사용할 수 있습니다.
- EXISTS, IN
- AND, OR, NOT
- =, >, >=, <, <=, <>
- BETWEEN, LIKE, IS NULL
집계 함수, 집합, 정렬
- 집계 함수
select
COUNT(m), //회원수
SUM(m.age), //나이 합
AVG(m.age), //평균 나이
MAX(m.age), //최대 나이
MIN(m.age) //최소 나이
from Member m
- 집합 - GROUP BY, HAVING
- 정렬 - ORDER BY
TypeQuery, Query
- TypeQuery: 반환 타입이 명확할 때 사용합니다.
- Query: 반환 타입이 명확하지 않을 때 사용합니다.
TypedQuery<Member> query = em.createQuery("SELECT m FROM Member m", Member.class);
Query query = em.createQuery("SELECT m.username, m.age from Member m");
//username은 string, age는 int 이기 떄문에 이 둘을 묶어서 명확한 타입을 지정할 수 없다.
결과 조회 API
- query.getResultList(): 결과가 하나 이상일 때 리스트를 반환합니다.
- 결과가 없다면 빈 리스트를 반환합니다.
TypedQuery<Member> query = em.createQuery("SELECT m FROM Member m", Member.class);
List<Member> resultList = query.getResultList();
- query.getSingleResult(): 결과가 정확히 하나 즉, 단일 객체 반환일 때 사용합니다.
- 결과가 없는 경우: javax.persistence.NoResultException 예외 발생
- 둘 이상일 경우: javax.persistence.NonUniqueResultException 예외 발생
TypedQuery<Member> query = em.createQuery("SELECT m FROM Member m where m.id=10", Member.class);
Member result = query.getSingleResult();
파라미터 바인딩
이름 기준
"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);
엔티티 직접 사용
기본 키
- JPQL에서 엔티티를 직접 사용하면 SQL에서 해당 엔티티의 기본 키 값을 사용합니다.
//JPQL
select count(m.id) from Member m /*엔티티의 아이디를 사용*/
select count(m) from Member m /*엔티티를 직접 사용*/
//SQL - 위의 JPQL 둘 다 같은 SQL 실행
select count(m.id) from Member m
[파라미터로 엔티티 전달 vs 식별자 직접 전달]
//파라미터로 엔티티 전달
String jpql = “select m from Member m where m = :member”;
Member findMember = em.createQuery(jpql)
.setParameter("member", member)
.getSingleResult();
//파라미터로 식별자 직접 전달
String jpql = “select m from Member m where m.id = :memberId”;
Member findMember = em.createQuery(jpql)
.setParameter("memberId", memberId)
.getSingleResult();
[실행된 SQL]
select m.* from Member m where m.id=?
외래 키
[파라미터로 엔티티 전달 vs 식별자 직접 전달]
Team team = em.find(Team.class, 1L);
//파라미터로 엔티티 전달
String qlString = “select m from Member m where m.team = :team”;
List resultList = em.createQuery(qlString)
.setParameter("team", team)
.getResultList();
//파라미터로 식별자 직접 전달
String qlString = “select m from Member m where m.team.id = :teamId”;
List resultList = em.createQuery(qlString)
.setParameter("teamId", teamId)
.getResultList();
[실행된 SQL]
select m.* from Member m where m.team_id=?
페이징 API
JPA는 페이징을 다음 두 API로 추상화하여 제공합니다.
- setFirstResult(int startPosition) : 조회 시작 위치 지정 (0부터 시작)
- setMaxResults(int maxResult) : 조회할 데이터 수 지정
[예시]
//페이징 쿼리
String jpql = "select m from Member m order by m.name desc";
List<Member> resultList = em.createQuery(jpql, Member.class)
.setFirstResult(10)
.setMaxResults(20)
.getResultList();
페이징 API는 방언에 따라 다른 SQL을 생성합니다.
[MySQL]
Hibernate:
SELECT
M.ID AS ID,
M.AGE AS AGE,
M.TEAM_ID AS TEAM_ID,
M.NAME AS NAME
FROM
MEMBER M
ORDER BY
M.NAME DESC LIMIT ?, ?
[Oracle]
Hibernate:
SELECT * FROM
( SELECT ROW_.*, ROWNUM ROWNUM_
FROM
( SELECT
M.ID AS ID,
M.AGE AS AGE,
M.TEAM_ID AS TEAM_ID,
M.NAME AS NAME
FROM MEMBER M
ORDER BY M.NAME desc
) ROW_
WHERE ROWNUM <= ?
)
WHERE ROWNUM_ > ?
JPQL 타입 표현
- 문자: ‘HELLO’, ‘She’’s’
- 문자는 작은 따옴표(') 안에 작성되어야 합니다.
- 작은 따옴표(')의 이스케이프 문자는 작은 따옴표(') 입니다.
- 숫자: 10L(Long), 10D(Double), 10F(Float)
- Boolean: TRUE, FALSE
String query = "select m.username, 'HELLO', TRUE From Member m";
List<Object[]> result = em.createQuery(query)
.getResultList();
for (Object[] objects : result) {
System.out.println("objects = " + objects[0]); //teamA
System.out.println("objects = " + objects[1]); //HELLO
System.out.println("objects = " + objects[2]); //true
}
- enum: jpabook.MemberType.Admin
- enum을 사용할 땐 패키지명을 포함해야 합니다.
package hellojpa.jpql;
public enum MemberType {
ADMIN, USER
}
"select m from Member m where m.memberType=hellojpa.jpql.MemberType.ADMIN"
String query = "select m from Member m where m.memberType=:userType";
List<Object[]> result = em.createQuery(query)
.setParameter("usreType", MemberType.ADMIN)
.getResultList();
- TYPE(i) = Book
- 엔티티 타입에 해당하는 타입을 조회할 수 있습니다.
- 상속 관계에서 사용합니다.
em.createQuery("select i from Item i where type(i) = Book", Item.class);
select *
from
Item item
where
item.DTYPE='BOOK'
다형성 쿼리
TYPE
- 조회 대상을 특정 자식으로 한정합니다.
- 예) Item 중에 Book, Movie를 조회해라
//JPQL
select i from Item i
where type(i) IN (Book, Movie)
//SQL
select i from i
where i.DTYPE in (‘B’, ‘M’)
TREAT(JPA 2.1)
- 자바의 타입 캐스팅과 유사합니다.
- 상속 구조에서 부모 타입을 특정 자식 타입으로 다룰 때 사용합니다.
- FROM, WHERE, SELECT(하이버네이트 지원) 사용
- 예) 부모인 Item과 자식 Book이 있다.
//JPQL
select i from Item i
where treat(i as Book).author = ‘kim’ //다운 캐스팅 (author 속성은 Book에만 존재)
//SQL
select i.* from Item i
where i.DTYPE = ‘B’ and i.author = ‘kim’ //싱글 테이블 전략에 해당 (다른 전략이라면 다른 쿼리 발생)
조건식 (Case 식)
기본 Case식
select
case when m.age <= 10 then '학생요금'
when m.age >= 60 then '경로요금'
else '일반요금'
end
from Member m
String query = "select " +
"case when m.age <= 10 then '학생요금' " +
" when m.age >= 60 then '경로요금' " +
"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일 경우 지정한 값을 반환합니다.
//회원 이름이 없다면 '이름 없는 회원'을 반환
select coalesce(m.username,'이름 없는 회원') from Member m
NULLIF
- 지정한 두 값이 같으면 null 반환을 반환하고, 다르면 첫번째 지정한 값을 반환합니다.
//회원 이름이 '관리자'라면 null을 반환하고, 아니라면 m.username 반환
select NULLIF(m.username, '관리자') from Member m
Named 쿼리
- 미리 정의해서 이름을 부여해두고 사용하는 JPQL입니다.
- 정적 쿼리만 가능합니다.
- 동적으로 문자 더하기하여 생성할 수 없습니다.
- 어노테이션, XML에 정의합니다.
- 장점: 애플리케이션 로딩 시점에 SQL로 변환하여 캐시하여 재사용합니다. → 미리 변환하여 캐시해놓으므로 SQL로 변환하는 비용이 줄어듭니다.
- 장점: 애플리케이션 로딩 시점에 쿼리를 검증합니다.
어노테이션
@Entity
@NamedQuery(
name = "Member.findByUsername",
query = "select m from Member m where m.username = :username")
public class Member {
...
}
List<Member> resultList = em.createNamedQuery("Member.findByUsername", Member.class)
.setParameter("username", "회원1")
.getResultList();
XML에 정의
[META-INF/persistence.xml]
<persistence-unit name="jpabook" >
<mapping-file>META-INF/ormMember.xml</mapping-file>
[META-INF/ormMember.xml]
<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings xmlns="http://xmlns.jcp.org/xml/ns/persistence/orm" version="2.1">
<named-query name="Member.findByUsername">
<query><![CDATA[
select m
from Member m
where m.username = :username
]]></query>
</named-query>
<named-query name="Member.count">
<query>select count(m) from Member m</query>
</named-query>
</entity-mappings>
Named 쿼리 환경에 따른 설정
- XML이 항상 우선권을 가집니다.
- 애플리케이션 운영 환경에 따라 다른 SQL을 사용하고 싶다면, 다른 XML을 배포하면 됩니다.
참고 스프링 데이터 JPA에서 네임드 쿼리를 간편하게 사용할 수 있습니다.public interface UserRepository extends JpaRepository<User, Long> { @Query("select u from User u where u.emailAddress = ?1" User findByEmailAddress(String emailAddress); }
기본 함수
- CONCAT (문자 더함)
String query = "select concat('a', 'b') from Member";
//String query = "select 'a' || 'b' from Member" //하이버네이트 제공, 위의 동일한 기능
---
s = ab
- SUBSTRING
String query = "select substring(m.username, 2, 3) from Member";
- TRIM
- LOWER, UPPER
- LENGTH
- LOCATE (인덱스 찾음)
String query = "select locate('de', 'abcdegf') from Member";
---
4
- ABS, SQRT, MOD
- SIZE (컬렉션의 사이즈), INDEX(JPA 용도)
String query = "select size(t.members) from Team t";
---
2
사용자 정의 함수
- JPQL에서 사용자 정의 함수를 사용할 수 있습니다.
- 하이버네이트에서는 사용하는 DB 방언을 상속받아 정의하고, 사용자 정의 함수를 등록합니다.
select function('group_concat', m.name) from Member m
실습
- DB 방언을 상속받아 사용자 정의 방언 생성
- 사용자 정의 함수 등록
package hellojpa.dialect;
...
public class MyH2Dialect extends H2Dialect {
public MyH2Dialect() {
registerFunction("group_concat", new StandardSQLFunction("group_concat", StandardBasicTypes.STRING));
}
}
- 방언 변경
<property name="hibernate.dialect" value="org.hibernate.dialect.MyH2Dialect"/>
String query = "select function('group_concat', m.username) from Member m";
//String query = "select group_concat(m.username) from Member m"); //위와 동일
List<String> result = em.createQuery(query, String.class)
.getResultList();
---
s = member1, member2
'Backend > JPA' 카테고리의 다른 글
[JPQL] 조인 (0) | 2023.12.24 |
---|---|
[JPQL] 프로젝션 (0) | 2023.12.24 |
[JPA] JPA의 다양한 쿼리 방법 소개 (0) | 2023.12.24 |
[JPA] 값 타입 (0) | 2023.12.20 |
[JPA] 프록시와 연관관계 관리 (0) | 2023.12.20 |