JPQL은 객체지향 쿼리 언어다.테이블을 대상으로 쿼리하는 것이 아니라 엔티티 객체를 대상으로 쿼리한다.
SQL을 추상화하여 특정 DB SQL에 의존하지 않는다.
JPQL은 결국 SQL로 번역된다.
SELECT m from Member as m where m.age > 18
1.엔티티(Member)와 속성(age)는 대소문자를 구분한다.
2.키워드(SELECT,FROM,where)은 대소문자를 구분하지 않는다.
3.엔티티 이름을 사용한다.(테이블x)
4.별칭은 필수(m) (as는 생략가능)
집계 함수
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
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
SELECT 절을 사용하여 원하는 특정 데이터(필드)만 선택해서 조회하는 것
(1) 전체 엔티티 프로젝션
SELECT m FROM Member m
(2) 임베디드 타입 프로젝션
SELECT m.address FROM Member m
(3) 스칼라 타입(특정 필드) 프로젝션
SELECT m.username, m.age FROM Member m
프로젝션 대상(결과)들은 영속성 컨텍스트에 의해 관리된다.
List<Member> result=em.createQuery("select m from Member m",Member.class).getResultList();
Member findMember=result.get(0);
findMember.setAge(20); // -> 실제 DB 데이터는 변경된다.
페이징(Paging)이란 대량의 데이터를 한 번에 가져오지 않고 일정한 크기로 나누어 가져오는 기법이다.
List<Member> result = em.createQuery("select m from Member m order by m.age desc", Member.class)
.setFirstResult(1)
.setMaxResults(10)
.getResultList();
조인(JOIN)은 두 개 이상의 테이블을 연결하여 관련된 데이터를 하나의 결과로 반환하는 SQL 연산이다.
1.내부 조인: 테이블에서 공통된 데이터(일치하는 행)만 반환한다.
2.외부 조인: 한쪽의 테이블은 모두 포함하고 일치하는 오른쪽 데이터를 반환한다.(없을시 NULL)
3.세타 조인: 관계가 없는 두 테이블을 cartesian 곱과 조건절로 반환한다.
ON 절: 조인할 두 테이블 간의 연관 관계를 명확하게 지정할 때 필요하다. 또한 연관관계가 없는 테이블에서도 ON을 통한 조인이 가능하다. 실제 SQL문에서 AND 조건으로 나타난다.
String query="select m from Member m left join m.team t on t.name='teamA'";
String query="select m from Member m left join Team t on m.username=t.name"
위 예시에서 첫번째 조인은 엔티티에 연관 필드인 Team 엔티티를 조인하고, 두번째 조인은 Team 테이블을 직접 조인하는 다양한 ON절을 활용할수 있다.
서브 쿼리(Sub Query)란 특정 쿼리로 인한 결과 값이 쿼리 문의 파라미터로 필요할때 사용한다.
서브 쿼리 함수
1.EXISTS (subquery): 서브쿼리에 결과가 존재하면 참
2.ALL: 주어진 조건이 서브쿼리의 모든 결과에 참인지 확인
3.ANY,SOME: 조건을 하나라도 만족하면 참
4.IN: 특정 값이 지정된 리스트나 서브쿼리의 결과에 포함되는지를 확인하는 비교 연산자
JPA 서브 쿼리 한계
JPQL 타입 표현
String query="select m.username,HELLO,true From Member m"+
"where m.type = jpashop.jpql.MemberType.ADMIN";
조건식
기본 CASE 식
select
case when m.age <= 10 then '학생요금'
when m.age >= 60 then '경로요금'
else '일반요금'
end
from Member m
단순 CASE 식
select
case t.name
when '팀A' then '인센티브110%'
when '팀B' then '인센티브120%'
else '인센티브105%'
end
from Team t
String query="select coalesce(m.username,'이름 없는 회원')"+"from Member m";
String query="select nullif(m.username,'관리자')"+"from Member m";
점(.)을 찍어 객체 그래프를 탐색하는 것
select m.username -> 상태 필드
from Member m
join m.team t -> 단일 값 연관 필드
join m.orders o -> 컬렉션 값 연관 필드
where t.name = '팀A'
String query="select t.members From Team t";
위 코드에서 컬렉션에 들어있는 여러 컬렉션에서 어떤 필드를 가져와야 하는지 모호하고 결과 값은 List가 반환되고 필드는 탐색하지 않는다. 다음과 같이 From 절에서 명시적 join으로 별칭을 얻어 컬렉션 필드에 대한 조회가 가능하다.
String query="select m From Team t join t.members m";
실무 조언
페치 조인은 연관된 엔티티들을 한번의 SQL 쿼리로 조회할수 있게 한다. 기본적으로 연관관계에 있는 엔티티들은 지연 로딩(Lazy Loading)으로 설정하고 접근하면 N+1 문제가 발생하지만 페치 조인은 JPQL을 통해 설정 상관없이 명시적으로 동적인 타이밍에 즉시 로딩(Eager Loading)으로 불러온다.
JPQL
select m from Member m join fetch m.team
SQL
select m.*,t.* From member m INNER JOIN TEAM T ON M.TEAM_ID=T.ID
예시
위와 같이 Member와 Team 엔티티가 있고 연관관계 외래키가 Team_id로 이루어져 있다.
fetch 조인 결과
페치 조인을 수행하면 한번의 쿼리로 위와 같이 Inner join이 수행된 결과 테이블을 반환한다.
영속성 컨텍스트
이후 영속성 컨텍스트엔 위와 같은 그림으로 저장된다.
String query="select m From Member m";
List<Member> result = em.createQuery(query, Member.class)
.getResultList();
for (Member m:result){
System.out.println("member= " +m.getUsername()+", "+m.getTeam().getName());
}
위와 같이 member에 대한 쿼리만 진행하고 getTeam으로 연관관계 필드에 접근하면 지연 로딩 설정으로 인해 쿼리 결과 개수인 N+1의 쿼리가 더 진행된다. 해당 문제를 해결하기 위해 다음과 같은 페치 조인을 수행한다.
String query="select m From Member m join fetch m.team";
연관 컬렉션 필드를 지연 로딩으로 설정했어도 쿼리문에서 fetch join을 하면 즉시 로딩이 우선이다.
One쪽의 컬렉션을 페치 조인 하면 하나의 엔티티가 해당 컬렉션의 요소 수만큼 중복되어 저장된다. 이 경우, JPA가 자동으로 중복을 제거하여 결과를 반환한다.
JPQL
select t from Team t join fetch t.members where t.name='팀A'
SQL
select t.*,m.* from Team T INNER JOIN MEMBER M ON T.ID=M.TEAM_ID WHERE T.NAME='팀A'
일반 조인(inner join)
1.Fetch Join과 달리 연관 Entity에 Join을 걸어도 실제 쿼리에서 SELECT 하는 Entity는 조회 되는 Entity만 조회하여 영속성 컨텍스트에 영속화
2.조회의 주체가 되는 Entity만 SELECT 해서 영속화하기 때문에 연관 데이터가 필요하지 않은 상황에서 사용됨(DB에서는 JOIN이 발생하지만, 애플리케이션에서는 필터링된 Team 엔티티만 반환된다.)
멤버를 가진 team만 조회할때(inner join)
String query="select t From Team t join t.members";
결과: 팀1,팀A
팀2,팀B
페치 조인(fetch join)
1.조회의 주체가 되는 Entity 이외에 Fetch Join이 걸린 연관 Entity도 함께 SELECT 하여 모두 영속화
2.연관 엔티티를 참조하더라도 이미 영속성 컨텍스트에 있기 때문에 N+1 쿼리 문제 해소
@BatchSize
엔티티의 연관된 컬렉션을 조회할 때, 여러 엔티티의 ID를 한 번에 모아서 IN 절을 사용해 해당 컬렉션의 모든 요소를 한 번의 쿼리로 가져온다. 이를 통해 페치 조인 없이 N+1 문제를 완화하고 JPA의 페이징 API와 함께 사용할 수 있다. -> 컬렉션 엔티티 조회 시점에 쿼리가 발생한다.
미리 정의해서 이름을 부여해두고 사용하는 JPQL이고 애플리케이션 로딩 시점에 쿼리를 파싱하기 때문에 문법 오류를 잡아주고 많은 이점이 있다.
public interface UserRepository extends JpaRepository<User,Long>{
@Query("select u from User u where u.emailAddress = ?1")
User findByEmailAddress(String emailAddress);
}
해당 findByEmailAddress를 실행하면 쿼리가 실행된다.
벌크 연산이란 여러 개의 데이터를 한 번에 수정(Update), 삭제(Delete)하는 작업이다. 일반적으로 JPA에서는 엔티티를 하나씩 변경하면 1차 캐시(영속성 컨텍스트)를 거쳐야 하므로 성능이 저하될 수 있다. 벌크 연산을 사용하면 SQL을 직접 실행하여 여러 데이터를 한 번에 처리할 수 있다.
int result = em.createQuery("update Member m set m.age=20")
.executeUpdate();
벌크 연산은 영속성 컨텍스트를 무시하고 데이터베이스에 직접 쿼리 하기 때문에 벌크 연산을 먼저 수행하거나 수행 후 영속성 컨텍스트를 초기화 해줘야한다. -> 1차 캐시를 먼저 조회하기 때문에 잘못된 값이 나올수 있다.
Flush 자동 호출 조건
commitqueryflush()