카테고리 없음

JPA (9) - JPQL (기본문법)

now0204 2024. 1. 12. 12:13

 

 

> jpql은 단순하게 조회가 가능하고, 객체 그래프 탐색으로 쿼리를 날릴 수 있다는 장점이 있다! 

> JPA는 엔티티 객체 중심 개발이므로, SQL또한 테이블이 아닌 엔티티 객체를 대상으로 검색하도록 돕는다.

> SQL을 추상화했다. (문법 매우 유사하다) -> JPQL은 엔티티 객체를 대상으로 쿼리를 한다.

 

*모든 DB 데이터를 객체로 변환하는 것은 불가능하다 -> 필요한 데이터만 DB에서 불러오려면 결국 검색 조건이 포함된 

SQL이 필요하다.

 

String jpql = "select m From Member m where m.name like '%hello%'";
List<Member> result = em.createQuery(jpql, Member.class).getResultList();

 

1. JPQL 기본 문법과 기능 

 

"select m from Member as m where m.age >18"

select 
  COUNT(m), SUM(m.age), AVG(m.age), MAX(m.age), MIN(m.age) from Member m

 

 > 엔티티와 속성은 대소문자 구분한다!! 

 > JPQL 키워드 대소문자X (SELCT ,FROM)

 > 엔티티 이름을 사용한다 (테이블 명이 아니다!)

 > 별칭은 필수 (as 생략 가능)

 > GROUP BY, HAING, ORDER BY 사용 가능 

 

1.1 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");

 

- 결과 조회 

 

query.getResultList() : 결과가 하나 이상 -> 결과가 없으면 빈 리스트 반환 

query.getSingleResult() : 결과가 정확히 하나, 단일 객체 반환

(결과 없으면: javax.persistence.NoResultException, 둘 이상이면 javax.persistence.NonUniqueResultException)

 

1.2 파라미터 바인딩 

 

SELECT m FROM Member m where m.username =:username 
query.setParameter("username",usernameParam);

 

> 위치 기반 바인딩도 있는데 그건 쓰지말자 

 

1.3 프로젝션

 

> SELECT 절에 조회할 대상을 지정하는 것 

> 프로젝션 대상 : 엔티티, 임베디드 타입, 스칼라 타입(숫자,문자등 기본 데이터 타입)

 

SELECT m FROM Member m // 엔티티 프로젝션
SELECT m.team FROM Member m // 엔티티 프로젝션
SELECT m.address FROM Member m //임베디드 타입 프로젝션 
SELECT m.username, m.age FROM Member m // 스칼라 타입 프로젝션 

 
 //엔티티 타입 프로젝션 -> 결과 영속성 관리 
 List<Member> selectMember = em.createQuery("select m from Member m where m.username=:username", Member.class)
                    .setParameter("username",member.getUsername())
                    .getResultList();
 Member findMember = selectMember.get(0);
 findMember.setAge(20);
 
 //Member안에 Team 조회 m.team으로 해도 묵시적 join발생 혹은 select m.team from Member (join Team t on m.MEMBER_ID = t.MEMBER_ID)
 //하지만 묵시적 조인은 추후에 알아보기 힘들기 때문에 명시적으로 조인을 써주자
 List<Member> selectMember2 = 
 em.createQuery("select t from Member m join m.team t", Member.class).getResultList();

 

> DISTINCT로 중복 제거 가능 

 

> 프로젝션으로 여러 값 조회(여러 스칼라와 같이)

 

SELECT m.username, m.age FROM Member m

 

 -> Query 타입으로 조회

Query query = em.createQuery("SELECT m.username, m.age FROM Member m");

 

 -> Object[] 타입으로 조회 (결과 하나당 Object[]배열에 담겨나옴)

List<Object[]> objectResult = em.createQuery("SELECT m.username, m.age FROM Member m").getResultList();

for(Object[] result : objectResult){

	//..result[0],result[1]
}

 

 -> new 명령어로 조회 (DTO 사용)

 

List<MemberDTO> results = em.createQuery("SELECT new jpql.MemberDTO(m.username,m.age) from Member m",MemberDTO.class)
                          .getResultList();

 

 > 패키지명 포함한 전체 클래스명 입력, 순서와 타입이 일치하는 생성자 필요하다.

 > 해당 데이터를 자주 조회하는 경우 위 방식을 고려해보자 

 

 

2. 페이징 API

 

> JPA는 페이징을 다음 두 API로 추상화

   setFirstResult(int startPosition) : 조회 시작 위치

   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();

 

3. 조인

 

내부조인 : SELECT m FROM Member m [INNER] JOIN m.team t;

외부조인 : SELECT m FROM Member m LEFT [OUTER] JOIN m.team t

세타 조인: SELECT count(m) FROM Meber m, Team t where m.username = t.name

 

> ON 절을 활용한 조인 

   - 조인 대상 필터링 

     - 연관관계 없는 엔티티 외부조인 

//회원과 팀을 조인하면서, 팀 이름이 A인 팀만 조인
SELECT m,t FROM Member m LEFT JOIN m.team t on t.name = 'A' // 약간 on 하기 전에 조건넣기


SELECT m,t FROM Member m LEFT JOIN Team t on m.username = t.name //연관관계 없는 열 조인

 

4. 서브쿼리 

 

//나이가 평균보다 많은 회원 (서브쿼리랑 메인쿼리 관련 없음 -> 성능상 좋다)
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

 

 

 -> 서브 쿼리 지원 함수 (EXSIST,ALL,ANY,SOM,IN 지원) 

 

//팀A 소속인 회원

SELECT m FROM Member m WHERE exsist(select t from m.team t where t.name ='팀A'); //m.team이면 조인해서 가져옴

//전체 상품 각각의 재고보다 주문량이 많은 주문들 
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도 하이버네이트)

 

 

5. JPQL 타입 표현  

 

> 문자 : 'HELLO', 'SEH''s' // '표현하고 싶으면 ''두개 

> 숫자 : 10(LONG), 10D(Double), 10F(Float)

> Boolean : TRUE, FALSE 

> ENUM : jpabook.MemberType.Admin (패키지명 포함)

> 엔티티타입: TYPE(m) = Member(상속관계에서 사용)

 

//ENUM타입
String query="select m.username,'HELLO',TRUE From Member m WHERE m.type = jpql.MemberType.ADMIN";
 String query="select m.username,'HELLO',TRUE From Member m WHERE m.type = :type";
 List<Object[]> result = em.createQuery(query).setParameter("type",MemberType.ADMIN).getResultList();

일케 바꾸기 가능 setPArameter

//상속관계에서 Item을 뽑는데 조건으로 자식 사용
 em.createQuery("select i from Item i where type(i) = Book");

 

 

> CASE식 

SELECT 
   case when m.age <= 10 then '학생요금'
   case when m.age >= 60 then '경로요금'
   else '일반요금'
  end
  from Member m
  
 SELECT 
  case t.name
  when '팀A' then
  //..
 end
 FROM Team t
 
 
 select coalesce(m.username, '이름 없는 회원') from Member m //하나씩 조회해서 null이 아니면 반환
 select NULLIF(m.username, '관리자') from Member m // 두 값이 같으면 null 다르면 첫번째 값 반환
 //유저명이 관리자면 null 아니면 유저명

 

 

> JPQL 기본 함수 (JPQL 표준 DB와 상관없이 사용가능)

 

- CONCAT, SUBSTRING, TRIM, LOWER, UPPER, LENGTH, LOCATE(IndexOf),ABS,SQRT,MOD

- SIZE,INDEX(JPA 용도)

 

* 사용자 정의 함수 호출 가능 -> 사용자 정의함수 만드는 방법은 필요시 찾아보자..

select function('group_concat',i.name) from Item i //사용자 정의함수 호출