객체지향 쿼리 언어1
목차
- 객체지향 쿼리 언어 소개
- JPQL
- 기본 문법과 기능
- 페치 조인
- 경로 표현식
- 다현성 쿼리
- 엔티티 직접 사용
- Named 쿼리
- 벌크 연산
1. 객체지향 쿼리 언어 소개
1) JPA 가 지원하는 쿼리 방법
- JPQL
- JPA Criteria
- QueryDSL
- 네이티브 SQL
- JDBC API 직접 사용, MyBatis, SpringJdbTemplate 함께 사용
2) JPQL
//검색
String jpql = "select m From Member m where m.name like ‘%hello%'";
List<Member> result = em.createQuery(jpql, Member.class)
.getResultList();
- 테이블이 아닌 객체를 대상으로 하는 객체 지향 쿼리
- SQL과 문법 유사
- 모든 DB데이터를 객체로 변환하여 검색하는 것은 불가능
- 애플리케이션이 필요한 데이터만 DB에서 불러오려면 결국 검색 조건이 포함된 SQL이 필요
- SQL을 추상화하여 특정 데이터베이스에 SQL에 의존x
- 동적 쿼리 작성이 힘듦
3) Criteria
//Criteria 사용 준비
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Member> query = cb.createQuery(Member.class);
//루트 클래스 (조회를 시작할 클래스)
Root<Member> m = query.from(Member.class);
//쿼리 생성 CriteriaQuery<Member> cq =
query.select(m).where(cb.equal(m.get("username"), “kim”));
List<Member> resultList = em.createQuery(cq).getResultList();
- 문자가 아닌 자바 코드로 JPQL을 작성
- JPQL 빌더 역할
- JPA의 공식 기능
- 너무 복잡하고 실용성이 없음(유지보수가 힘듦)
- 코드가 직관적이지 않음
4) QureryDSL
//JPQL
//select m from Member m where m.age > 18
JPAFactoryQuery query = new JPAQueryFactory(em);
QMember m = QMember.member;
List<Member> list =
query.selectFrom(m)
.where(m.age.gt(18))
.orderBy(m.name.desc())
.fetch();
- 문자가 아닌 자바 코드로 JPQL 작성
- JPQL 빌더 역할
- 컴파일 시점에 문법 오류를 찾을 수 있음
- 동적 쿼리 작성 편리함
- 실무에서 자주 사용함
- 코드가 직관적임
5) 네이티브 SQL
String sql =
"SELECT ID, AGE, TEAM_ID, NAME FROM MEMBER WHERE NAME = ‘kim’";
List<Member> resultList =
em.createNativeQuery(sql, Member.class).getResultList();
- JPA가 제공하는 SQL을 직접 사용하는 기능
- JPQL로 해결할 수 없는 특정 데이터베이스에 의존적인 기능
- 예) 오라클 CONNECT BY, 특정 DB만 사용하는 SQL 힌트
6) JDBC 직접 사용, SpringJdbcTemplate
- JPA를 사용하면서 JDBV 커넥션을 직접 사용하거나, 스프링 JdbcTeplat, 마이바티스등을 함께 사용 가능
- 단 영속성 컨텍스트를 적절한 시점에 강제로 플러시 필요
- 예) JPA를 우회해서 SQL을 실행하기 직전에 영속성 컨텍스트를 수동으로 플러시
2. JPQL - 기본 문법과 기능
- JPQL은 객체지향 쿼리 언어
- 엔티티 객체를 대상으로 쿼리
- SQL을 추상화 하기 때문에 특정 데이터베이스 SQL에 의존하지 않음
- JPQL은 결국 SQL로 변환됨
1) JPQL 문법
select_문 :: =
select_절
from_절
[where_절]
[groupby_절]
[having_절]
[orderby_절]
update_문 :: = update_절 [where_절]
delete_문 :: = delete_절 [where_절]
- select m from Member as m where m.age > 18
- 엔티티와 속성은 대소문자 구분O (Member, age)
- JPQL 키워드는 대소문자 구분X (SELECT, FROM, where)
- 엔티티 이름 사용, 테이블 이름이 아님(Member)
- 별칭은 필수(m) (as는 생략가능)
2) 집합과 정렬
select
COUNT(m), //회원수
SUM(m.age), //나이 합
AVG(m.age), //평균 나이
MAX(m.age), //최대 나이
MIN(m.age) //최소 나이
from Member m
3) 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");
4) 결과 조회 API
- query.getResultList(): 결과가 하나 이상일때, 리스트 반환
- 결과가 없으면 빈 리스트 반환
- query.getSingleResult(): 결과가 정확히 하나, 단일 객체 반환
- 결과가 없으면 NoResultException
- 둘 이상이면 NonUniqueResultException
5) 파라미터 바인딩 - 이름, 위치 기준
- 이름기준
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);
- 이름기준을 사용하자.
- 위치 기준으로 하면 순서 혼동 및 위치 변동이 어려움.
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 -> 스칼라 타입 프로젝션
- DISTINCT로 중복 제거
1) 프로젝션 - 여러 값 조회
- SELECT m.username, m.age FROM Member m
- Query 타입으로 조회
Member member = new Member();
member.setUsername("member1");
member.setAge(10);
em.persist(member);
em.flush();
em.clear();
Query query = em.createQuery("select m.username, m.age from Member m");
List resultList = query.getResultList();
Object o = resultList.get(0);
Object[] result = (Object[]) o;
System.out.println("m.username = " + result[0]);
System.out.println("m.age = " + result[1]);
- Object[] 타입으로 조회
Member member = new Member();
member.setUsername("member1");
member.setAge(10);
em.persist(member);
em.flush();
em.clear();
List<Object[]> resultList = em.createQuery("select m.username, m.age from Member m")
.getResultList();
Object[] result = resultList.get(0);
System.out.println("m.username = " + result[0]);
System.out.println("m.age = " + result[1]);
- new 명령어로 조회
Member member = new Member();
member.setUsername("member1");
member.setAge(10);
em.persist(member);
em.flush();
em.clear();
List<MemberDTO> memberDTOS = em.createQuery("select new jpql.MemberDTO(m.username, m.age) from Member m", MemberDTO.class)
.getResultList();
MemberDTO memberDTO = memberDTOS.get(0);
System.out.println("m.username = " + memberDTO.getUsername());
System.out.println("m.age = " + memberDTO.getAge());
- 단순 값을 DTO로 바로 조회 SELECT new jpabook.jpql.UserDTO(m.username, m.age) FROM Member m
- 패키지 명을 포함한 전체 클래스 명 입력
- 순서와 타입이 일치하는 생성자 필요
public MemberDTO(String username, int age) {
this.username = username;
this.age = age;
}
4. 페이징 API
페이징: 한 화면에서 보여주는 데이터의 범위를 결정하는 방법
커서: select문을 통해 결과값들이 메모리 공간에 저장되는데 이 메모리 공간을 커서라 한다.
Fetch: 커서에서 원하는 결과값을 추출하는 것.
- JPA는 페이징을 다음 두 API로 추상화
- setFirstResult(int startPosition): 조회 시작 위치(0부터 시작)
- setMaxResults(int maxResult): 조회할 데이터 수
1) 사용예시
//페이징 쿼리
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();
5. 조인
내부 조인
: SELECT m FROM Member m [INNER] JOINm.team
t외부 조인
: SELECT m FROM Member m LEFT [OUTER] JOINm.team
t세타 조인
: select count(m) from Member m,Team
t where m.username = t.name
1) 조인 - ON
- JPA 2.1부터 지원
- 조인대상 필터링
- 연관관계 없는 엔티티 외부 조인(하이버네이트 5.1부터 지원)
2) 조인 대상 필터링
- 회원과 팀을 조읺하면서, 팀 이름이 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'
3) 연관관계 없는 엔티티 외부 조인
회원의 이름과 팀의 이름이 같은 대상 외부 조인
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
6. 서브 쿼리
- 나이가 평균보다 많은 회원
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
1) 서브 쿼리 지원 함수
- [NOT] EXISTS: 서브쿼리에 결과가 존재하면 참
- ALL: 모두 만족하면 참
- ANY, SOME: 하나라도 만족하면 참
- [NOT] IN: 하나라도 같은 것이 있으면 참
2) 예제
- 팀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)
3) JPA 서브 쿼리 한계
- JPA는 WHERE, HAVING 절에서만 서브 쿼리 사용 가능
- 하이버네이트에서는 SELECT절 까지 지원
- FROM 절의 서브쿼리는 현재 JPQL에서 불가능
- 조인으로 풀 수 있으면 풀어서 해결
7. JPQL 타입 표현
- 문자: 'HELLO', 'she''s'
- 숫자: 10L(Long), 10D(Double), 10F(Float)
- Boolean: TRUE, FALSE
- ENUM: jpabook.MemberType.Admin(패키지명 포함)
- 엔티티 타입: TYPE(m) = Member (상속 관계에서 사용(DTYPE))
1) 기타
- SQL과 문법이 같음
- EXISTS, IN
- AND, OR, NOT
- =, >, >=, <, <=, <>
- BETWEEN, LIKE, IS NULL
8. 조건식
- 기본 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이 아니면 반환
사용자 이름을 조회하여 null이 아니면 반환, null 이면 이름 없는 회원 반환
select coalesce(m.username,'이름 없는 회원') from Member m
NULLIF: 두 값이 같으면 null 반환, 다르면 첫번째 값 반환
사용자 이름이 관리자면 null 반환, 나머지는 본인 이름 반환(관리자면 이름ㅇ르 숨기는 기능)
select NULLIF(m.username, '관리자') from Member m
1) JPQL 기본 함수
- CONCAT
- 여러 문자열을 하나의 문자열로 혹은 여러 컬럼을 하나의 문자열로 합치는 함수
- SUBSTRING
- SUBSTRING(str,pos,len)의 형태로 사용하며 문자열을 자르는 함수
- TRIM
- 문자열에 공백을 제거하는 함수인데 모든 공백이 아닌 좌우 공백만 제거한다.
- LOWER, UPPER
- 문자를 소문자 대문자로 변경하는 함수
- LENGTH
- 문자열의 길이를 가져오는 함수
- LOCATE
- str에 있는 문자열의 substr 검색 위치를 정수로 반환하는데, substr이 str에 없으면 0을 반환하는 함수
- ABS, SQRT, MOD
- ABS: 절대값을 구하는 함수
- SQRT: 제곱근을 반환
- MOD: 나머지를 반환
- SIZE, INDEX(JPA 용도)
- 컬렉션의 크기를 구함
- LIST 타입 컬렉션의 위치값을 구하는 함수
2) 사용자 정의 함수 호출
H2 데이터베이스 경우 H2Dialect 클래스에 JPQL 함수를 정의해 놓았다.
H2 DB에 존재하는 함수인데 H2Dialect 클래스에 미처 정의하지 못한 함수들이 있다.
이런 경우 방언에 함수를 추가해야하며 이 추가한 함수를 사용자 정의 함수라고 한다.
하이버네이트는 사용전 방언에 추가해야 함(persistence.xml)
<property name="hibernate.dialect" value="dialect.MyH2Dialect"/>
사용하는 DB 방언을 상속받고, 사용자 정의 함수를 등록해야함
public class MyH2Dialect extends H2Dialect { public MyH2Dialect(){ this.registerFunction("group_concat", new StandardSQLFunction("group_concat", StandardBasicTypes.STRING)); } }
select function('group_concat', i.name) from Item i
'JPA > JPA Basic' 카테고리의 다른 글
[JPA] 10. 객체지향 쿼리 언어2 (0) | 2023.02.04 |
---|---|
[JPA] 8. 값 타입 (0) | 2023.01.31 |
[JPA] 7. 프록시와 연관관계 관리 (0) | 2023.01.31 |
[JPA] 6. 고급 매핑 (0) | 2023.01.29 |
[JPA] 5. 다양한 연관관계 매핑 (0) | 2023.01.29 |
[JPA] 참고. @JoinColumn (0) | 2023.01.29 |
[JPA] 참고. Entity, DAO, DTO, VO (0) | 2023.01.27 |
[JPA] 에러. SQL Error 23505 (0) | 2023.01.27 |