JPA를 사용하면 엔티티 객체를 중심으로 개발할 수 있다.
하지만 SQL문을 사용할 때, 또 다시 테이블 중심으로 쿼리문을 작성하게 된다.
이를 해결하기 위해 JPA는 SQL을 추상화한 JPQL이라는 객체 지향 쿼리 언어를 제공한다.
JPQL은 SQL문법과 유사하지만, 객체를 대상으로 쿼리한다는 특징이 있다.
JPQL은 SQL을 추상화해서 특정 데이터베이스 SQL에 의존하지 않는다.
ex)
String jpql = "select m from Member m where m.age > 18";
List<Member> result = em.createQuery(jpql, Member.class)
.getResultList();
실행된 SQL
select
m.id as id,
m.age as age,
m.USERNAME as USERNAME,
m.TEAM_ID as TEAM_ID
from
Member m
where
m.age>18
JPQL만 사용하면 동적 쿼리 작성이 복잡하고, 쿼리문의 문법 오류를 찾기 힘들다는 단점이 있는데 이 점을 보완한 것이 QueryDSL이다.
실무에서 사용이 권장되므로 나중에 공부해보도록 하자
select m from Member as m where m.age > 18
엔티티와 속성은 대소문자 구분
ex) Member, name
엔티티 이름 사용
별칭 필수 (as 생략 가능)
select
COUNT(m), //회원수
SUM(m.age), //나이 합
AVG(m.age), //평균 나이
MAX(m.age), //최대 나이
MIN(m.age) //최소 나이
from Member m
• GROUP BY, HAVING
GROUP BY : 특정 컬럼으로 그룹화
HAVING : 그룹화 한 결과에 조건 걸기
• ORDER BY
query.getResultList()
: 결과가 하나 이상일 때, 결과가 없으면 빈 리스트 반환
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);
프로젝션 : select절에서 조회할 대상을 지정하는 것
엔티티 프로젝션
select m from Member m
임베디드 타입 프로젝션
select m.address from Member m
스칼라 타입 프로젝션
select m.username from Member m
여러값 조회
- Object[] 타입으로 조회
List<Object[]> result = em.createQuery("select m.username, m.age from Member m")
.getResultList();
- DTO로 조회
public class UserDTO{
private String username;
privaet int age;
}
List<UserDTO> result = em.createQuery("select new JPA.XXX.XX..UserDTO(m.username, m.age) from Member m", UserDTO.class)
.getResultList();
- new + 패키지 명을 포함한 전체 클래스 명 입력
- 순서와 타입이 일치하는 생성자가 필요하다.
• setFirstResult(int startPosition) : 조회 시작 위치
(0부터 시작)
• setMaxResults(int maxResult) : 조회할 데이터 수
ex)
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()
JPQL을 이용하면 방언에 맞게 처리해주어서 페이징을 비교적 간편하게 할 수 있다.
• 내부 조인 : 둘 이상의 테이블에 존재하는 공통 속성의 값이 같은 것을 결과로 추출
SELECT m FROM Member m [INNER] JOIN m.team t
• 외부 조인 : 왼쪽 투플 기준으로 자연조인 시 실패한 투플을 모두 보여주되 값이 없는 대응 속성에는 NULL 값을 채워서 반환
SELECT m FROM Member m LEFT [OUTER] JOIN m.team t
• 세타 조인 : 조인에 참여하는 두 릴레이션의 속성 값을 비교하여 조건을 만족하는 투플만 반환
select count(m) from Member m, Team t where m.username
= t.name
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
세타 조인과는 외부조인 한다는 점에서 다른 것 같다.
ex) 나이가 평균보다 많은 회원
select m from Member m
where m.age > (select avg(m2.age) from Member m2)
• [NOT] EXISTS (subquery): 서브쿼리에 결과가 존재하면 참
• {ALL | ANY | SOME} (subquery)
• ALL 모두 만족하면 참
• ANY, SOME: 같은 의미, 조건을 하나라도 만족하면 참
• [NOT] IN (subquery): 서브쿼리의 결과 중 하나라도 같은 것이 있으면 참
JPQL에서 FROM절 서브쿼리는 현재 불가능하다.
CASE WHEN 구문은 SELECT절에 쓰이며, 대표적으로 2가지 쓰임새가 있다.
select
case when m.age <= 10 then '학생요금'
when m.age >= 60 then '경로요금'
else '일반요금'
end
from Member m
SELECT A, B, COALESCE(A,B) FROM table_a;
select NULLIF(m.username, '관리자') from Member m
.
을 찍어 객체 그래프를 탐색한다.• 상태 필드(state field): 단순히 값을 저장하기 위한 필드
(ex: m.username)
select m.username, m.age from Member m
• 연관 필드(association field): 연관관계를 위한 필드
연관 필드를 탐색하면 묵시적 내부조인이 발생한다.
- 단일 값 연관 필드:
@ManyToOne, @OneToOne, 대상이 엔티티 (ex: m.team)
• JPQL: select o.member from Order o
• SQL:
select m.* from Orders o
inner join Member m on o.member_id = m.id
- 컬렉션 값 연관 필드:
@OneToMany, @ManyToMany, 대상이 컬렉션 (ex: t.members)
FROM 절에서 명시적 조인을 통해 별칭을 얻어야 탐색이 가능하다.
select m.username from Team t join t.members m
가급적 묵시적 조인 대신 명시적 조인을 사용하자
묵시적 조인은 일어나는 상황을 한눈에 파악하기 힘들다.
중요하다.
ex)
[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
select distinct t from Team t join fetch t.members where t.name = ‘팀A’
일반 조인은 데이터를 이용하지만 실제로 엔티티를 가져오지는 않는다.
하지만 페치 조인을 이용하면 연관 엔티티를 함께 즉시로딩으로 조회하여, 객체 그래프를 구성한다.
• [JPQL]
select count(m.id) from Member m //엔티티의 아이디를 사용
select count(m) from Member m //엔티티를 직접 사용
• [SQL](JPQL 둘다 같은 다음 SQL 실행)
select count(m.id) as cnt from Member m
@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();
UPDATE, DELETE
String qlString = "update Product p " +
"set p.price = p.price * 1.1 " +
"where p.stockAmount < :stockAmount";
int resultCount = em.createQuery(qlString)
.setParameter("stockAmount", 10)
.executeUpdate();