SELECT m FROM Member AS m where m.username =
Hello``
TypeQuery
TypeQuery
사용Member
엔티티로 명확하다.TypeQuery<Member> query = em.createQuery("SELECT m FROM Member m", Member.class);
List<Member> resultList = query.getResultList();
for (Member member : resultList) {
System.out.println("member = " + member);
}
Query
m.username
, m.age
두개임Query query = em.createQuery("SELECT m.username, m.age from Member m");
List resultList = query.getResultList();
for (Object o : resultList) {
Object[] result = (Object[]) o; //결과가 둘 이상이라 배열
System.out.println("username = " + result[0]);
System.out.println("age = " + result[1]);
}
query.getResultList()
query.getSingleResult()
NoResultException
예외가 발생한다.NonUniqueResultException
예외가 발생한다.이름 기준 파라미터
:username
으로 이름 기준 파라미터를 정의한다.query.setParameter()
에서 파라미터를 바인딩한다.String usernameParam = "User1";
TypeQuery<Member> query =
em.createQuery("SELECT m FROM Member m where m.username = :username", Member.class);
query.setParameter("username", usernameParam);
List<Member> resultList = query.getResultList();
//체인 방식
List<Member> members =
em.createQuery("SELECT m FROM Member m where m.username = :username", Member.class)
.setParameter("username", usernameParam)
.getResultList();
위치 기준 파라미터
List<Member> members =
em.createQuery("SELECT m FROM Member m where m.username = ?1", Member.class)
.setParameter(1, usernameParam)
.getResultList();
엔티티 프로젝션
SELECT m FROM Member m //회원
SELECT m.team FROM Member m //팀
임베디드 타입 프로젝션
//잘못된 쿼리
String query = "SELECT a FROM Address a";
//정상적인 쿼리
String query = "SELECT o.address FROM Order o";
List<Address> addresses = em.createQuery(query, Address.class).getResultList();
스칼라 타입 프로젝션
List<String> usernames =
em.createQuery("SELECT username FROM Member m", String.class)
.getResultList();
Double orderAmountAvg =
em.createQuery("SELECT AVG(o.orderAmount) FROM Order o", Double.class)
.getSingleResult()
여러 값 조회
List<Object[]> resultList =
em.createQuery("SELECT o.member, o.product, o.orderAmount FROM Order o")
.getResultList();
for (Object[] row : resultList) {
Member member = (Member) row[0]; //엔티티
Product product = (Product) row[1]; //엔티티
int orderAmount = (Integer) row[2]; //스칼라
}
New 명령어
TypeQuery
로 명확하게 받을 수 있어서 객체 변환 작업을 줄일 수 있다.jpabook.jpql.UserDTO
)UserDTO(m.username, m.age)
)public class UserDTO {
private String username;
private int age;
public UserDTO(String username, int age) {
this.username = username;
this.age = age;
}
...
}
//New 명령어 사용 전
List<Object[]> resultList =
em.createQuery("SELECT m.username, m.age FROM Member m")
.getResultList();
List<UserDTO> userDTOs = new ArrayList<UserDTO>();
for (Object[] row : resultList) {
UserDTO userDTO = new UserDTO((String)row[0], (Integer)row[1]);
userDTOs.add(userDTO);
}
return userDTOs;
//New 명령어 사용 후
TypeQuery<UserDTO> query =
em.createQuery("SELECT new jpabook.jpql.UserDTO(m.username, m.age) FROM Member m");
List<UserDTO> resultList = query.getResultList();
return resultList;
JPA는 페이징 처리를 두 API로 추상화했다.
TypeQuery<Member> query =
em.createQuery("SELECT m FROM Member m ORDER BY m.username DESC", Member.class);
query.setFirstResult(10);
query.setMaxResults(20);
query.getResultList();
select
COUNT(m), //회원수
SUM(m.age), //나이 합
AVG(m.age), //평균 나이
MAX(m.age), //최대 나이
MIN(m.age) //최소 나이
from Member m
GROUP BY
는 통계 데이터를 구할 때 특정 그룹끼리 묶어준다.SELECT t.name, COUNT(m.age), SUM(m.age), AVG(m.age), MAX(m.age), MIN(m.age)
FROM Member m
LEFT JOIN m.team t
GROUP BY t.name
HAVING
은 GROUP BY
와 함께 사용되며, 그룹화한 통계 데이터를 기준으로 필터링한다.SELECT t.name, COUNT(m.age), SUM(m.age), AVG(m.age), MAX(m.age), MIN(m.age)
FROM Member m
LEFT JOIN m.team t
GROUP BY t.name
HAVING AVG(m.age) >= 10
이런 쿼리들을 보통 리포팅 쿼리 혹은 통계 쿼리라고 한다.
통계 쿼리는 전체 데이터를 기준으로 처리하므로 실시간으로 사용하기엔 부담이 많다.
select m from Member m order by m.age DESC, m.username ASC
String teamName = "팀A";
String query = "SELECT m FROM Member m INNER JOIN m.team t "
+ "where t.name = :teamName";
List<Member> members = em.createQuery(query, Member.class)
.setParameter("teamName", teamName)
.getResultList();
//JPQL
SELECT m
FROM Member m INNER JOIN m.team t
where t.name = '팀A';
//SQL
SELECT
M.ID AS ID,
M.AGE AS AGE,
M.TEAM_ID AS TEAM_ID,
M.NAME AS NAME
FROM
MEMBER M INNER JOIN TEAM T ON M.TEAM_ID = T.ID
WHERE
T.NAME = ?
//JPQL
SELECT m
FROM Member m LEFT [OUTER] JOIN m.team t
//SQL
SELECT
M.ID AS ID,
M.AGE AS AGE,
M.TEAM_ID AS TEAM_ID,
M.NAME AS NAME
FROM
MEMBER M LEFT OUTER JOIN TEAM T ON M.TEAM_ID = T.ID
WHERE
T.NAME = ?
SELECT t, m From Team t LEFT JOIN t.members m
JPQL에서 성능 최적화를 위해 제공하는 기능이다.
연관된 엔티티나 컬렉션을 한 번에 같이 조회하는 기능이며 join fetch
명령어를 통해 사용한다.
member.getTeam().name()
을 사용해도 지연 로딩 발생 안 함(프록시 객체X)//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
//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'
DISTINCT
를 추가하고, 애플리케이션에서 한번 더 중복을 제거//JPQL
select distinct t
from Team t join fetch t.members
where t.name = '팀A'
//애플리케이션 출력 결과
teamname = 팀A, team = Team@0x100
->username = 회원1, member = Member@0x200
->username = 회원2, member = Member@0x200
DISTINCT
를 추가하여도 데이터가 다르므로 효과가 없음//내부 조인 JPQL
select t
from Team t join t.member m
where t.name = '팀A'
//실행된 SQL
SELECT T.*
FROM TEAM T
INNER JOIN MEMBER M ON T.ID=M.TEAM_ID
WHERE T.NAME = '팀A'
//페치 조인의 경우
SELECT T.*, M.*
FROM TEAM T
INNER JOIN MEMBER M ON T.ID=M.TEAM_ID
WHERE T.NAME = '팀A'
.
을 찍어 객체 그래프를 탐색하는 것
o.member
를 통해 주문에서 회원으로 단일 값 연관 필드로 경로 탐색
단일 값 연관 필드로 경로 탐색을 하면 내부 조인이 일어나는데 이것을 묵시적 조인이라고 한다.
묵시적 조인은 모두 내부 조인이다.
//JPQL
select o.member from Order o
//실행 SQL
select m.*
from Orders o
inner join Member m on o.member_id=m.id //묵시적 조인 (모두 내부 조인)
JPQL에서 많이 하는 실수 중 하나는 컬렉션 값에서 경로 탐색을 시도하는 것이다.
컬렉션에서 경로 탐색을 더 하고 싶다면 조인을 사용해서 새로운 별칭을 획득해야 한다.
select t.members.username from Team t //실패
select m.username from Team t join t.members m //성공
서브 쿼리는 WHERE, HAVING 절에서만 사용할 수 있다.
//나이가 평균보다 많은 회원 조회
select m from Member m
where m.age > (select avg(m2.age) from Member m2)
서브쿼리에 결과가 존재하면 참이다. NOT은 반대
팀A 소속인 회원
select m from Member m
where exists (select t from m.team t where t.name = '팀A')
서브쿼리의 결과 중 하나라도 같은 것이 있으면 참이다.
20세 이상을 보유한 팀
select t from Team t
where t IN (select t2 from Team t2 JOIN t2.members m2 where m2.age >= 20)
//주문이 하나라도 잇는 회원 조회
select m from Member m
whehre m.orders is not empty
//memberparam이 컬렉션에 포함되어 있는지 확인
select t from Team t
where :memberparam member of t.members
select
case
when m.age <= 10 then '학생요금'
when m.age >= 60 then '경로요금'
else '일반요금'
end
from Member m
select
case t.name
when '팀A' then '인센티브110%'
when '팀B' then '인센티브120%'
else '인센티브105%'
end
from Team t
JPQL로 부모 엔티티를 조회하면 그 자식 엔티티도 함께 조회한다.
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "DTYPE")
public abstract class Item {...}
@Entity
@DiscriminatorValue("B")
public class Book extends Item {
...
private String author;
}
//Album, Movie 생략
List resultList = em.createQuery("select i from Item i").getResultList();
//단일 테이블 전략(InheritanceType.SINGLE_TABLE) 을 사용한 경우
SELECT * FROM ITEM
//조인 전략(InheritanceType.JOINED)을 사용한 경우
SELECT
I.ITEM_ID, ...,
B.AUTHOR, ...,
A.ARTIST, ...,
M.ACTOR, ...,
FROM ITEM I
LEFT OUTER JOIN
BOOK B ON I.ITEM_ID=B.ITEM_ID
LEFT OUTER JOIN
ALBUM A ON I.IOTEM_ID=A.ITEM_ID
LEFT OUTER JOIN
MOVIE M ON I.ITEM_ID=M.ITEM_ID
조회 대상을 특정 자식 타입으로 한정할 때 사용한다.
//JPQL
select i from Item i
where type(i) IN (Book, Movie)
//SQL
SELECT I FROM ITEM I
WHERE I.DTYPE IN ('B', 'M')
//JPQL
select m from Member m where m = :member
//SQL
select m.*
from Member m
where m.id = ?
//JPQL
select m from Member m where m.team = :team
//SQL
select m.*
from Member m
where m.team_id = ?
JPQL쿼리는 크게 동적 쿼리와 정적쿼리로 나눌 수 있다.
em.createQuery("select ....")
처럼 런타임에 특정 조건에 따라 JPQL을 동적으로 구성@NamedQuery
어노테이션을 사용하거나 XML 문서에 작성하여 사용@Entity
@NamedQuery(
name = "Member.findByUsername",
query = "select m from Member m where m.username = :username")
public class Member {
...
}
//NamedQuery 사용
List<Member> memberList = em.createNamedQuery("Member.findByUsername",
Member.class)
.setParametmer("username", "회원1")
.getResultList();