- JPA는 다양한 쿼리 방법을 지원
- JPQL
: SQL과 유사한 객체 중심의 쿼리 언어- QueryDSL
: SQL과 같은 쿼리를 생성하는 빌더
(JPQL의 단점인 동적쿼리 등을 커버하기 위한 용도로 사용)- JPA Criteria
: JPA에서 표준으로 지원하는 SQL생성 빌더 (하지만, 복잡해서 안씀)- Native SQL
: 실제 SQL (MySQL, Oracle등)- JDBC API 직접사용 / My Batis / SpringJdbc Template
- JPA에서
표준
으로등록
되어 있지만, 사용이 불편해서 잘 쓰지 않음- 대안으로
QueryDSL
을 사용//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();
- 문자가 아닌
Java 코드
로JPQL
을 작성- JPQL 빌더
컴파일 시점
에서 오류를 찾을 수 있음- 설정하는 과정이 필요하다! (
의존성 추가
등등)동적쿼리 사용
이 편리(JPQL의 단점)- 실무사용에서 거의 필수적 사용
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에서 실제 SQL을 사용하는 것
- JPQL과 QueryDSL로 해결할 수 없는 기능을 구현할 때 사용됨
String sql = “SELECT ID, AGE, TEAM_ID, NAME FROM MEMBER WHERE NAME = ‘kim’"; List<Member> resultList = em.createNativeQuery(sql, Member.class).getResultList();
- 역시, JPQL과 QueryDSL로 해결할 수 없는 기능을 구현할 때 사용됨
영속성 컨텍스트
를 적절한 시점에강제 flush()
해줘야 함
- 왜?
: 해당 기술들은JPA와 상관없는 기능
들이라서 JPA 로직상영속성 컨텍스트
를 통해서영속화
를 통해 바로 실제 DB에 접근하지 않기에 올바른 로직으로 수행하기 위해 적절한flush()
를 해줘야 함
[ 설명 ]
Entity 객체
를 대상으로 사용하는객체 지향 쿼리 언어
- 일반
SQL
은테이블 중심
,JPQ
은객체 중심
- SQL과 유사한 문법,
ANSI 표준
기능을 제공- SQL을 추상화해서 특정
DataBase
에 의존하지 않음
(방언
을 바꿔도 알아서JPA
가 해줌)
[ 기본 규칙 ]
- SQL과 매우 유사한 문법
select_문 :: = select_절 from_절 [where_절] [groupby_절] [having_절] [orderby_절] update_문 :: = update_절 [where_절] delete_문 :: = delete_절 [where_절]
- Entity와 속성은
대소문자 구분 O
(Member
,age
)- JPQL 키워드는
대소문자 구분 X
(SELECT
,from
)- 테이블 이름이 아닌 Entity 이름을 사용
- 별칭은 필수로 작성 (
as
키워드는 생략 가능)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
- 정렬
select m from Member m group by m.name // 이름으로 그룹 order by m.id // 정렬은 id 순서
- TypeQuery
: 반환 타입이 명확할 때 사용/* 반환 타입이 정확히 Member 클래스 --> 반환 클래스 파라미터로 지정! */ TypedQuery<Member> query = em.createQuery("SELECT m FROM Member m", Member.class);
- Query
: 반환 타입이 명확하지 않을 때 사용/* 반환 타입이 정확히 Member 클래스 --> 2번째 파라미터 없음 */ Query query = em.createQuery("SELECT m.name, m.age FROM Member m");
query.getResultList()
: 결과가 하나 이상일 때, 리스트 반환
결과가 없는 경우
->빈 리스트
반환
query.getSingleResult()
: 결과가 정확히 하나 일 때, 단일 객체 반환
결과가 없는 경우
-> 오류javax.persistence.NoResultException
(추후java 8
의Optional
로 처리 가능)결과가 둘 이상인 경우
-> 오류Javax.persistence.NonUniqueResultException
파라미터 바인딩
방법
: 기본적으로:변수명
+setParameter()
를 사용
- 이름 기준 :
변수 이름
을 기준으로 바인딩- 위치 기준 :
파라미터 위치 번호
를 기준으로 바인딩/* 이름 기준 파라미터 바인딩 -- 이름으로 바인딩 위치를 찾음 */ em.createQuery("select m from Member m where m.name=:name", Member.class) .setParameter("name","정욱").getResultList(); /* 위치 기준 파라미터 바인딩 -- 파라미터 위치 순서에 맞게 지정 */ em.createQuery("select m from Member m where m.name=?1", Member.class) .setParameter(1,"정욱").getResultList();
단순 값 조회
프로젝션
종류
엔티티
프로젝션 (조회 대상-> 엔티티 )
:SELECT m FROM Member m
임베디드 타입
프로젝션 (조회 대상-> 임베디드 타입)
:SELECT m.address FROM Member m
스칼라 타입
프로젝션 (조회 대상 -> 스칼라 타입)
:SELECT m.name, m.age FROM Member m
(스칼라 타입
: 숫자, 문자등 기본 데이터 타입)여러 값 조회
- 방법
Query 타입
으로 조회 (타입 캐스팅 해야함 --> 불편)Object[] 타입
으로 조회 (과정이 번잡함 --> 불편)new 명령어
로 조회 --> 자주 방법 사용/* Query 타입으로 조회 --> 타입이 정해져있지 않아서 Object 배열로 들억가 있음 */ Query query = em.createQuery("select m.username, m.age from Member m"); Object o = query.getResultList().get(0); Object[] result = (Object[]) o; System.out.println(result[0]); // m.username System.out.println(result[1]); // m.age /* Object[] 타입으로 조회 */ List<Object[]> resultList = em.createQuery("select m.username, m.age from Member m").getResultList(); Object[] result = resultList.get(0); System.out.println(result[0]); // m.username System.out.println(result[1]); // m.age /* new 명령어로 조회 */ public class UserDTO{ private String username; private int age; /* 생성자가 있어야 값을 받을 수 있음 */ public UserDTO(String username, int age){ this.username = username; this.age = age; } } // 데이터를 받을 DTO 생성 /* new jpabook.jpql.UserDTO(m.username, m.age) 이렇게 pacakage명을 전부 입력해줘야 함 */ TypedQuery<UserDTO> query = em.createQuery("select new jpabook.jpql.UserDTO(m.username, m.age) from Member m", UserDTO.class); List<UserDTO> resultList = query.getResultList(); UserDTO userDTO = resultList.get(0); System.out.println(userDTO.getUsername); // m.username System.out.println(userDTO.getUser); // m.age
참고
엔티티 프로젝션
의 경우가져온 모든 결과
는영속성 컨텍스트
에 의해 관리
--> 값을 수정하면persist
없이 바로update query
가 생성
설명
- 특정 데이터의 구역을 나눠서 가져오는
페이징
이라고 함
(1페이지, 2페이지 데이터를 구분해서 가져오는 것)- JPA사용으로 매우 간편해짐
(JPA가 아니면 3중 쿼리나, 추가 옵션 이런게 많음)- 페이징 메서드
setFirstResult(int startPosition)
: 조회 시작 위치 (0부터 시작)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) // 11번째부터 (0부터 시작이니까) .setMaxResults(20) // 20개를 가져와라 .getResultList();
방언별 페이징 API
: 페이징 수행시 실제나가는 쿼리
- MySQL 방언
SELECT M.ID AS ID, M.AGE AS AGE, M.TEAM_ID AS TEAM_ID, M.NAME AS NAME FROM MEMBER M ORDER BY M.NAME DESC LIMIT ?, ?
- Oracle 방언
SELECT * FROM ( SELECT ROW_.*, ROWNUM ROWNUM_ FROM ( SELECT M.ID AS ID, M.AGE AS AGE, M.TEAM_ID AS TEAM_ID, M.NAME AS NAME FROM MEMBER M ORDER BY M.NAME ) ROW_ WHERE ROWNUM <= ? ) WHERE ROWNUM_ > ?
Inner / outer / theta JOIN
- 내부조인(inner join) , 외부조인(outer join)
/* inner join */ List<Member> innerJoin = em.createQuery ("select m from Member m inner join m.team t", Member.class) .getResultList(); /* 결과 */ Hibernate: select 생략 .. from Member member0_ inner join Team team1_ on member0_.team_id=team1_.team_id =============================================================== /* outer join */ List<Member> leftOuterJoin = em.createQuery ("select m from Member m left join m.team t", Member.class) .getResultList(); /* 결과 */ Hibernate: select 생략 .. from Member member0_ left outer join Team team1_ on member0_.team_id=team1_.team_id
- 세타 조인(theta join)
/* theta join */ em.persist(new Member("A", 10)); em.persist(new Member("B", 10)); em.persist(new Member("C", 10)); em.persist(new User("A")); em.persist(new User("B")); List<Member> resultList = em.createQuery ("select m from Member m, User u where m.name = u.name", Member.class) .getResultList(); /* 결과 */ Hibernate: select 생략... from Member member0_ cross join User user1_ where member0_.name=user1_.name
ON 절을 활용한 JOIN
- ON절 (
JPA 2.1부터 지원
) 활용
- 조인 대상 필터링
: 조인을 하기 전 조인할 대상을 미리 필터링- 연관관계 없는 엔티티
외부 조인
(Hibernate 5.1 지원
)
: 서로 연관관계가 없는 엔티티끼리 외부 조인을 할 수 있음
- 조인 대상 필터링 - 예시
/* 조인 대상 필터링 */ List<Member> resultList = em.createQuery ("select m from Member m left join m.team t on t.teamName = m.name", Member.class) .getResultList(); // 같은 쿼리를 where로 했을 때 List<Member> resultList = em.createQuery ("seelct m from Member m left join m.team t where t.teamName = m.name", Member.class) .getResultList();
: on절 뒤에 조건을 추가해서 대상을 필터링 함
- 연관관계 없는 엔티티
외부 조인
- 예시/* 연관관계가 없는 엔티티 조인 */ List<Member> result = em.createQuery ("select m from Member m left join User u on m.name = u.name", Member.class) .getResultList();
: on절 뒤에 추가적인 조건으로 join 조건을 명시 (서로 관계가 없어도)
설명
- JPA에서 서브쿼리를 사용할 수 있지만
WHERE
,HAVING
에서만 가능
(FROM
절에서 사용할 수 없다)- Hibernate에서 지원하면
SELECT
절에서도 사용할 수 있음FROM
절에서 사용할 수없는 문제에 대한 대안
- 1안 --> JOIN으로 풀어서 해결 (권장)
- 2안 --> 두개의 쿼리로 분리
- 3안 --> 나온 결과값을 java코드로 조작해서 필요한 데이터 추출
지원 함수
[NOT] EXISTS
: 결과가 존재하면 참
ALL
: 모두 만족하면 참ANY
/SOME
: 조건을 하나라도 만족하면 참[NOT]IN
: 결과중 하나라도 같은것이 있으면 참
예시
/* EXISTS - 팀A 소속인 회원 조회 */ select m from Member m where EXISTS (select t from m.team t where t.name = '팀A') /* ALL - 전체 상품 각각의 재고량보다 주문량이 많은 주문들*/ select o from Order o where o.orderAmount > ALL (select p.stockAmount from Product p) /* ANY - 어떤 팀이든 팀에 소속된 회원 */ select m from Member m where m.team = ANY(select t from Team t)
- 문자
:'
'
로 표현
ex)'HELLO'
,'She''s'
- 숫자
: L(Long) D(Double) F(Float)
ex)10L
/10D
/10F
- Boolean
:TRUE
/FALSE
- ENUM
: 패키지명 포함
ex)jpabook.MemberType.Admin
- ENUM을 패키지명으로 접근해 사용
- ENUM을 setParameter로 적용
- 기타
EXISTS
/IN
/AND
OR
NOT
/BETWEEN
/LIKE
/IS NULL
CASE
- 조건에 따라
case
를 나누는 식- 방법
- 기본 CASE 식
: 구간을 나눠서 값을 검증- 단순 CASE 식
: 특정 값으로 값을 검증 (exact
)
COALESCE
: 하나씩 조회해서 null이 아니면 그대로 반환
/* 값이 없는 회원은 '이름 없는 회원' 으로 나옴 */ select coalesce(m.name, '이름 없는 회원') from Member m
NULLIF
: 값이 같으면 null반환 (반환하지 않는 것), 다르면 그대로 반환
/* 사용자 이름이 '관리자'면 null반환(반환하지 않는 것), 나머지는 그대로 반환 */ select NULLIF(m.name, '관리자') from Member m
표준 함수
CONCAT
: 문자열 2개 더하는 함수 (||
로도 표현할 수 있음 )
SUBSTRING
TRIM
: 공백 제거 함수LOWER
/UPPER
LENGTH
LOCATE
: 내가 원하는게 원본 어디에 있는지 위치 반환하는 함수
ABS
/SQRT
/MOD
SIZE
/INDEX
사용자 정의 함수
- 직접 함수를 만드는 것
- 과정
- MyDialect 생성
- persistence.xml 변경
- 사용