JPQL
검색을 할 떄도 테이블이 아닌 엔티티 객체를 대상으로 검색 -> 데이터베이스 SQL에 의존 하지 않는다.
애플리케이션이 필요한 데이터만 DB에서 불러오려면 결국 검색 조건이 포함된 SQL이 필요
SQL과 문법유사, select ,from , where , group by, having , join지원
List<Member> resultList = em.createQuery(
"select m From Member m where m.username like '%kim%'",
Member.class
).getResultList();
객체 Member 로부터 조회하는 것을 확인 할 수 있다. 또한 JPQL 은 단순한 String 임으로 validate하기 어렵다.
Hibernate:
/* select
m
From
Member m
where
m.username like '%kim%' */ select
member0_.MEMBER_ID as member_i1_7_,
member0_.createdBy as createdb2_7_,
member0_.createdDate as createdd3_7_,
member0_.lastModifiedBy as lastmodi4_7_,
member0_.lastModifiedDate as lastmodi5_7_,
member0_.city as city6_7_,
member0_.street as street7_7_,
member0_.zipcode as zipcode8_7_,
member0_.LOCKER_ID as locker_12_7_,
member0_.TEAM_ID as team_id13_7_,
member0_.USERNAME as username9_7_,
member0_.endDate as enddate10_7_,
member0_.startDate as startda11_7_
from
Member member0_
where
member0_.USERNAME like '%kim%'
이런식으로 내부적으로 native sql 을 보내준다.
QueryDSL
문자가 아닌 자바코드로 JPQL을 작성할 수 있음
컴파일 시점에 문법 오류를 찾을 수 있다.
동적 쿼리 작성이 편리하다.
# em.flush() 는 commit()직전에도 날라가지만 query (예를들면 createNativeQuery ) 가 날라갈 때도 호출이된다. 하지만 JPA기술 관련이 아닌것을 사용하면 flush()가 호출이 안됨으로 manual 하게 호출해줘야한다.
기본 문법과 쿼리 API
JPQL 문법
select m from Member as m where m.age >18
엔티티와 속성은 대소문자 구분 한다.(Member, age)
JPQL 키워드는 대소문자 구분 하지 않는다.(select , from , where)
엔티티 이름 사용, 테이블 이름이 아님 (Member)
별칭은 필수(m) as는 생략이 가능하다.
TypeQuery = 반환 타입이 명확할 때 사용
Query =변환 타입이 명확하지 않을 때 사용
TypedQuery<Member> query1 = em.createQuery("select m from Member m", Member.class);
TypedQuery<Member> query2 = em.createQuery("select m.username from Member m", String.class);
Query query3 = em.createQuery("select m.username , m.age from Member m");
말 그대로다 Query의 경우에는 username, age를 동시에 반환하기 타입이 명확하지 않다.
결과 조회 API
query.getResultList() = 결과가 하나 이상일 때, 리스트 반환
- 결과가 없으면 빈 리스트 반환
query.getSingleResult() = 결과가 정확히 하나, 단일 객체 반환
- 결과가 없으면 javax.persistence.NoResultException
- 둘 이상이면 javax.persistence.NonUniqueResultException
Spring Data JPA에서는 Null, Optional 로 반환해준다. Exception을 터지지 않게 해줌
내부적으로 try catch 로 잡아서 return 해주는것이다.
파라미터 바인딩 - 이름 기준, 위치 기준
TypedQuery<Member> query1 = em.createQuery("select m from Member m where m.username = :username", Member.class);
query1.setParameter("username","member1");
Member singleResult = query1.getSingleResult();
위치기반은 쓰지 않는다. 위에처럼 이름 기준을 쓰도록 하자.
프로젝션
select 절에 조회할 대상을 지정하는것
엔티티, 임베디드 타입, 스칼라 타입 (숫자, 문자등 기본데이터 타입)
Distinct 로 중복 제거가능
select m from Member m
엔티티 프로젝션으로 여기서 나오는 모든 List<Member> 들은 영속성관리가 된다. 즉 mebmer.setage(10) 이런식으로 바꿔주면 알아서 더티체크 해서 update 쿼리를 날려준다.
그래서 이름이 엔티티 프로젝션이다. 불러온 entity들은 다 persistence context의 관리대상이다.
프로젝션 - 여러 값 조회
select m.username, m.age from Member as m
new 명령어로 조회.
단순값을 DTO로 바로 조회
List<MemberDTO> resultList = em.createQuery("select new jpql.MemberDTO(m.username,m.age) from Member m ", MemberDTO.class)
.getResultList();
패키지명을 포함한 전체 클래스 명 입력해야한다.
순서와 타입이 일치하는 생성자가 필요하다.
페이징 API
JPA는 페이징을 다음 두 API로 추상화
setFirstResult(int startPosition) = 조회 시작 위치 (0부터 시작)
setMaxResults(int maxResult) = 조회할 데이터 수
List<Member> resultList = em.createQuery("select m from Member m order by m.age desc", Member.class)
.setFirstResult(0)
.setMaxResults(10)
.getResultList();
내부적으로 database 방언에 맞게 페이징 쿼리를 날려준다.
조인
내부조인
select m from Member m [Inner] JOIN m.team t
회원은 있고 team이 없는 경우에는 -> 조회가 되지 않음
외부조인
select m from Member m LEFT [Outer] JOIN m.team t
회원은 있고 team이 없는 경우에는 -> team 이 null인상태로 column이 생김
세타조인
select count(m) from Member m , Team t
where m.username = t.name (cartesian multiplication)
아무 연관관계 없는 테이블을 조회 할때 주로 쓰임
조인 대상 필터링
예) 회원과 팀을 조인하면서, 팀 이름이 A인 팀만 조인
JPQL
select m ,t from Member m
left join m.Team t on t.name="A"
SQL
select m.* , t.* from Member m
left join Team t on m.TEAM_ID = t.id and t.name="A"
연관관계 없는 엔티티 외부 조인
예) 회원의 이름과 팀의 이름이 같은 대상 외부 조인
JPQL
select m , t from Member m
left join Team t on m.username = t.name
SQL
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)
한건이라도 주문한 고객
select m from Member m
where (select count(o) from Order o where m = o.member) > 0
서브 쿼리 지원함수
[NOT] EXISTS (subquery) = 서브 쿼리에 결과가 존재하면 참
{ALL | ANY | SOME} (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는 Where , Having 절에서만 서브 쿼리 사용 가능
select 절도 가능(하이버네이트에서 지원)
From 절의 서브 쿼리는 현재 JPQL에서 불가능하다.
조건 - case 식
기본 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
Coalesce = 하나씩 조회해서 null 이 아니면 반환
사용자 이름이 없으며 이름 없는 회원을 반환
select coalesce(m.username, '이름 없는 회원' ) from Member m
Nullif = 두 값이 같으면 null 반환, 다르면 첫번쨰 값 반환
사용자 이름이 '관라자' 면 null을 반환하고 나머지는 본인의 이름을 반환
select NULLIF(m.username, '관리자') from Member m
사용자 정의 함수 호출
하이버네이트는 사용전 방언에 추가 해야한다.
사용하는 DB방언을 상속 받고, 사용자 정의 함수를 등록한다.
public class MyH2Dialect extends H2Dialect
{
public MyH2Dialect()
{
registerFunction("group_concat",new StandardSQLFunction("group_concat", StandardBasicTypes.STRING));
}
}
이것은 실제 H2Dialect 소스코드들을 참조해서 작성하면된다. 불러올수 있는 함수들은 현재 H2 를 쓰고있으니 안에다 function 을 정의해서 쓰면된다. 그후에는 persistance.xml 에서 <property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/>
이것을 MyH2Dialect로 바꾸어주면된다.
String query = "select function('group_concat',m.username) from Member m";
List<String> query1 = em.createQuery(query, String.class)
.getResultList();
for (String s : query1)
{
System.out.println("s = " + s);
}
Hibernate:
/* select
function('group_concat',
m.username)
from
Member m */ select
group_concat(member0_.username) as col_0_0_
from
Member member0_
s = member1,member2
결과물로 member의 username 이 이어져서 나왔다.
'WEB > JPA' 카테고리의 다른 글
실전! 스프링 부트와 JPA 활용2 (API 개발기본 , 지연로딩과 조회 성능 최적화) (0) | 2021.04.03 |
---|---|
김영한 (ORM 표준 JPA 프로그래밍 11) 객체지향 쿼리 언어 소개2 - 중급문법 (0) | 2021.03.20 |
김영한 (ORM 표준 JPA 프로그래밍 9) 값 타입 (0) | 2021.03.16 |
김영한 (ORM 표준 JPA 프로그래밍 8) 프록시와 연관관계 관리 (0) | 2021.03.14 |
김영한 (ORM 표준 JPA 프로그래밍 7) 고급 매핑 (0) | 2021.03.13 |