Reference. 자바 ORM 표준 JPA 프로그래밍
책 목차 및 이전 글
더보기
들어가기 전 JPA 특징, Q&A
1. JPA 소개
3. 영속성 관리
4. 엔티티 매핑
4.1 - 4.3 @Entity, @Table, 다양한 매핑
4.4 - 4.5 데이터베이스 스키마 자동 생성, DDL 생성 기능
5. 연관관계 매핑 기초
6. 다양한 연관관계 매핑
7. 고급매핑
8. 프록시와 연관관계 관리
9. 값 타입
9.3~5 값 타입과 불변 객체, 값 비교, 값 타입 컬렉션
10. 객체지향 쿼리 언어
10.3 Criteria
- JPQL을 자바 코드로 작성하도록 도와주는 빌더 클래스 API
- Criteria 장점
- 문법 오류를 컴파일 단계에서 확인이 가능
- 문자 기반의 JPQL보다 동적 쿼리를 안전하게 생성
- Criteria 단점
- 코드가 복잡하고 장황해서 직관적이지 못해 이해하기 불편
10.3.1 Criteria 기초
Criteria API는 javax.persistence.criteria 패키지에 존재
기본 Criteria 쿼리
//JPQL: select m from Member m
CriteriaBuilder cb = em.getCriteriaBuilder(); //Criteria 쿼리 빌더 -- 1
//Criteria 생성, 반환 타입 지정 -- 2
CriteriaQuery<Member> cq = cb.createQuery(Member.class);
Root<Member> m = cq.from(Member.class); //FROM절 -- 3
cq.select(m); //SELECT 절 -- 4
TypedQuery<Member> query = em.createQuery(cq);
List<Member> members = query.getResultList();
- Criteria 쿼리는 빌더(CriteriaBuilder) 필요, EntityManager나 EntityManagerFactory로 생성
- Criteria 쿼리 빌더에서 Criteria 쿼리(CriteriaQuery)를 생성
- FROM 절을 생성, 반환된 값 m은 Criteria에서 사용하는 특별한 별칭, m을 조회의 시작점이라는 의미로 쿼리 루트(Root)로 사용
- SELECT 절을 생성
검색 조건 추가
//JPQL
/*
select m from Member m
where m.username = '회원1'
order by m.age desc
*/
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Member> cq = cb.createQuery(Member.class);
Root<Member> m = cq.from(Member.class);
//검색 조건 정의 -- 1
Predicate usernameEqual = cb.equal(m.get("username"), "회원1");
//정렬 조건 정의 -- 2
javax.persistence.criteria.Order ageDesc = cb.desc(m.get("age"));
//쿼리 생성 -- 3
cq.select(m)
.where(usernameEqual) //WHERE 절 생성
.orderby(ageDesc); //ORDER BY 절 생성
List<Member> resultList = em.createQuery(cq).getResultList();
- m.get("username")은 회원엔티티 별칭 m을 사용하여 JPQL에 m.username과 같은 표현 cb.equal(A,B)는 A=B를 의미 JPQL에 m.username = '회원1'과 같은 표현
- cb.desc(m.get("age"))는 JPQL의 m.age desc와 같은 표현
- 만들어둔 조건을 where, orderBy에 넣어 쿼리 생성
숫자 타입 검색
//select m from Member m
//where m.age > 10
//order by m.age desc
Root<Member> m = cq.from(Member.class);
//타입 정보 필요
Predicate ageGt = cb.greaterThan(m.<Integer>get("age"), 10); -- 1
//메소드 체이닝 없이 사용
cq.select(m);
cq.where(ageGt);
cq.orderBy(cb.desc(m.get("age")));
m.get("age")
은 타입 정보를 모르기 떄문에 제네릭으로 반환 타입 명시가 필요greaterThan()
대신gt()
사용 가능
10.3.2 Criteria 쿼리 생성
- CriteriaBuilder에 반환타입에 생성 쿼리 3가지 방법
- 엔티티, 임베디드 타입, 기타 반환
<T> CriteriaQuery<T> createQuery(Class<T> resultClass)
CriteriaBuilder cb = em.getCriteriaBuilder(); //Member를 반환타입으로 지정 CriteriaQuery<Member> cq = cb.createQuery(Member.class); ... //Member타입으로 지정했으므로 지정하지 않아도 Member 타입으로 반환 List<Member> resultList = em.createQuery(cq).getResultList();
- Object 반환
CriteriaQuery<Object> createQuery()
//Object로 조회 CriteriaBuilder cb = em.getCriteriaBuilder(); CriteriaQuery<Object> cq = cb.createQuery(); //Object를 반환 ... List<Object> resultList = em.createQuery(cq).getResultList(); //Object[]로 조회 CriteriaBuilder cb = em.getCriteriaBuilder(); CriteriaQuery<Object[]> cq = cb.createQuery(Object[].class); //Object[]를 반환 ... List<Object[]> resultList = em.createQuery(cq).getResultList();
- Tuple 반환
CriteriaQuery<Tuple> createTupleQuery()
CriteriaBuiolder cb = em.getCriteriaBuilder(); CriteriaQuery<Tuple> cq = cb.createTupleQuery(); //Tuple을 반환 ... TypeQuery<Tuple> query = em.createQuery(cq);
10.3.3 조회
- 조회 대상을 한건만 지정하는 경우
cq.select(m) //JPQL: select m
- 조회 대상을 여러건 지정하는 경우
//JPQL: select m.username, m.age cq.multiselect(m.get("username"), m.get("age")); //cb.array를 사용 CriteriaBuilder cb = em.getCriteriaBuilder(); cq.select( cb.array(m.get("username"), m.get("age")) );
DISTINCT
- select. multiselect 다음에 distinct(true)를 사용해서 설정
cq.multiselect(m.get("username"), m.get("age")).distinct(true);
NEW, construct();
- JPQL에
select new 생성자()
을 Criteria에선cb.construct(클래스 타입, ..)
로 사용//JPQL: // select new jpabook.domain.MemberDTO(m.username, m.age) from Meber m CriteriaQuery<MemberDTO> cq = cb.createQuery(MemberDTO.class); Root<Member> m = cq.from(Member.class); //construct 구문 cq.select(cb.construct(MemberDTO.class, m.get("username"), m.get("age"))); TypedQuery<MemberDTO> query = em.createQuery(cq); List<MemberDTO> resultList = query.getResultList();
튜플
- Map과 비슷한 튜플이라는 특별한 반환 객체를 제공
- 튜플은 이름 기반이므로 순서 기반의 Object[]보다 안전
tuple.getElements()
메소드로 현재 튜플의 별칭과 자바 타입 조회도 가능튜플 기본 사용
//JPQL: select m.username, m.age from Member m CriteriaBuilder cb = em.getCriteriaBuilder(); CriteriaQuery<Tuple> cq = cb.createTupleQuery(); //CriteriaQuery<Tuple> cq = cb.createQuery(Tuple.class); //위와 동일 Root<Member> m = cq.from(Member.class); cq.multiselect( m.get("username").alias("username"), //튜플에서 사용할 튜플 별칭 m.get("age").alias("age") ); TypedQuery<Tuple> query = em.createQuery(cq); List<Tuple> resultList = query.getResultList(); for (Tuple tuple : resultList) { //튜플 별칭으로 조회 String username = tuple.get("username", String.class); Integer age = tuple.get("age", Integer.class); }
튜플 엔티티 조회
//JPQL: select m, m.username from Member m CriteriaQuery<Tuple> cq = cb.createTupleQuery(); Root<Member> m = cq.from(Member.class); //cq.multiselect //아래 cq.select 결과 동일 cq.select(cb.tuple( m.alias("m"), //회원 엔티티, 별칭 m m.get("username").alias("username") )); List<Tuple> resultList = em.createQuery(cq).getResultList(); for (Tuple tuple : resultList) { Member member = tuple.get("m", Member.class); String username = tuple.get("username", String.class); }
- 엔티티, 임베디드 타입, 기타 반환
10.3.4 집합
GROUP BY
GROUP BY 기본 사용법
/*
JPQL:
select m.team.name, max(m.age), min(m.age)
from Member m
group by m.team.name
*/
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Object[]> cq = cb.createQuery(Object[].class);
Root<Member> m = cq.from(Member.class);
Expression maxAge = cb.max(m.<Integer>get("age"));
Expression minAge = cb.min(m.<Integer>get("age"));
cq.multiselect(m.get("team").get("name"), maxAge, minAge);
cq.groupBy(m.get("team").get("name")); //GROUP BY
TypedQuery<Object[]> query = em.createQuery(cq);
List<Object[]> resultList = query.getResultList();
cq.groupBy(m.get("team").get("name"))
은 JPQL에group by m.team.name
과 동일
HAVING
cq.multiselect(m.get("team").get("name"), maxAge, minAge)
.groupBy(m.get("team").get("name"))
.having(cb.gt(minAge, 10)); //HAVING
having(cb.gt(minAge, 10))
은 JPQL에hgaving min(m.age) > 10
과 동일
10.3.5 정렬
cb.desc(...)
또는cb.asc(...)
로 생성cq.select(m) .where(ageGt) .orderBy(cb.desc(m.get("age"))); //JPQL: order by m.age desc //정렬 API 1. CriteriaQuery<T> orderBy(Order... o); 2. CriteriaQuery<T> orderBy(List<Order> o);
10.3.6 조인
- 조인은
join()
메소드와JoinType
클래스를 사용public enum JoinType { INNER, //내부조인 LEFT, //왼쪽 외부 조인 RIGHT //오른쪽 외부 조인, JPA 구현체나 데이터베이스에 따라 지원 안될 수 있음 }
사용 예시
/* JPQL select m, t from Member m inner join m.team t where t.name = '팀A' */ Root<Member> m = cq.from(Member.class); Join<Member, Team> t = m.join("team", JoinType.INNER); //내부 조인 cq.multiselect(m, t) .where(cb.equal(t.get("name"), "팀A")); //----조인 방법---- 1. m.join("team") //내부 조인 (JoinType.INNER 생략 가능) 2. m.join("team", JoinType.LEFT) //LEFT 조인 3. m.fetch("team", JoinType.LEFT) //FETCH JOIN
10.3.7 서브 쿼리
간단한 서브 쿼리
/* JPQL:
select m from Member m
where m.age >=
(select AVG(m2.age) from Member m2)
*/
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Member> mainQuery = cb.createQuery(Member.class);
//서브 쿼리 생성
Subquery<Double> subQuery = mainQuery.subquery(Double.class);
Root<Member> m2 = subQuery.from(Member.class);
subQuery.select(cb.avg(m2.<Integer>get("age")));
//메인 쿼리 생성
Root<Member> m = mainQuery.from(Member.class);
mainQuery.select(m)
.where(cb.ge(m.<Integer>get("age"), subQuery))
상호 관련 서브 쿼리
- 상호 관련 서브쿼리란 메인 쿼리와 서브 쿼리 간에 서로 관련이 있는 경우를 뜻함
- 서브 쿼리에서 메인 쿼리의 정보를 사용하려면 메인 쿼리에 사용한 별칭이 필요
서브 쿼리는 메인 쿼리의 Root나 Join을 통해 사용
.where(cb.equal(subM.get("username"), m.get("username")));
상호 관련 서브 쿼리 사용 예시
/* JPQL select m from Member m where exists (select t from m.team t where t.name = '팀A') */ CriteriaBuilder cb = em.getCriteriaBuilder(); CriteriaQuery<Member> mainQuery = cb.createQuery(Member.class); //서브 쿼리에서 사용되는 메인 쿼리 m Root<Member> m = mainQuery.from(member.class); //서브 쿼리 생성 Subquery<Team> subQuery = mainQuery.subquery(Team.class); Root<Member> subM = subQuery.correlate(m); //메인 쿼리의 별칭을 가져옴 Join<Member, Team> t = subM.join("team"); subQuery.select(t) .where(cb.equal(t.get("name"), "팀A")); //메인 쿼리 생성 mainQuery.select(m) .where(cb.exists(subQuery)); List<Member> resultList = em.createQuery(mainQuery).getResultList();
subQuery.correlate(m)
을 사용하면 메인 쿼리의 별칭(m) 사용 가능
10.3.8 IN 식
/* JPQL:
select m from Member m
where m.username in ("회원1", "회원2")
*/
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Member> cq = cb.createQuery(Member.class);
Root<Member> m = cq.from(Member.class);
cq.select(m)
.where(cb.in(m.get("username"))
.value("회원1")
.value("회원2"));
10.3.9 CASE 식
/* JPQL:
select m.username
case when m.age>=60 then 600
when m.age<=15 then 500
else 1000
end
from Member m
*/
Root<Member> m = cq.from(Member.class);
cq.multiselect(
m.get("username"),
cb.selectCase()
.when(cb.ge(m.<Integer>get("age"), 60), 600)
.when(cb.le(m.<Integer>get("age"), 15), 500)
.otherwise(1000)
);
10.3.10 파라미터 정의
- JPQL에 :PARAM1처럼 파라미터 정의가 가능
/* JPQL select m from Member m where m.username = :usernameParam */ CriteriaBuilder cb = em.getCriteriaBuilder(); CriteriaQuery<Member> cq = cb.createQuery(Member.class); Root<Member> m = cq.from(Member.class); cq.select(m) .where(cb.equal(m.get("username"), cb.parameter(String.class, "usernameParam"))); List<Member> resultList = em.createQuery(cq) .setParameter("usernameParam", "회원1") .getResultList();
- 하이버네이트는 파라미터 정의하지 않고 직접 값 입력해도 파라미터 바인딩을 사용
cq.select(m) .where(cb.equal(m.get("username"), "회원1")); //실행 SQL select * from Member m where m.name=?
10.3.11 네이티브 함수 호출
Root<Member> m = cq.from(Member.class);
Expression<Long> function = cb.function("SUM", Long.class, m.get("age"));
cq.select(function);
10.3.12 동적 쿼리
- 다양한 검색 조건에 따라 실행 시점에 쿼리를 생선하는 것을 의미
//------------JPQL 동적 쿼리------------ Integer age = 10; String username = null; String teamName = "팀A"; //JPQL 동적 쿼리 생성 StringBuilder jpql = new StringBuilder("select m from Member m join m.team t "); List<String> criteria = new ArrayList<String>(); if (age != null) criteria.add(" m.age = :age "); if (username != null) criteria.add(" m.username = :username "); if (teamName != null) criteria.add(" t.name = :teamName "); if (criteria.size() > 0 ) jpql.append(" where "); for (int i = 0; i< criteria.size(); i++) { if(i > 0) jpql.append(" and "); jpql.append(criteria.get(i)); } TypeQuery<Member> query = em.createQuery(jpql.toString(), Member.class); if (age != null) query.setParameter("age", age); if (username != null) query.setParameter("username", username); if (teamName != null) query.setParameter("teamName", teamName); List<Member> resultList = query.getResultList(); //------------Criteria 동적 쿼리------------ Integer age = 10; String username = null; String teamName = "팀A"; CriteriaBuilder cb = em.getCriteriaBuilder(); CriteriaQuery<Member> cq = cb.createQuery(Member.class); Root<Member> m = cq.from(Member.class); Join(Member, Team> t = m.join("team"); List<Predicate> criteria = new ArrayList<Predicate>(); if (age != null) criteria.add(cb.equal(m.<Integer>get("age"), cb.parameter(Integer.class, "age"))); if (username != null) criteria.add(cb.equal(m.get("username"), cb.parameter(String.class, "username"))); if (teamName != null) criteria.add(cb.equal(t.get("name"), cb.parameter(String.class, "teamName"))); cq.where(cb.and(criteria.toArray(new Predicate[0]))); TypedQuery<Member> query = em.createQuery(cq); if (age != null) query.setParameter("age", age); if (username != null) query.setParameter("username", username); if (teamName != null) query.setParameter("teamName", teamName); List<Member> resultList = query.getResultList();
10.3.13 함수 정리 (p.424-425 참조)
- Criteria는 JPQL 함수들을 코드로 지원
- 조건 함수
- 스칼라와 기타 함수
- 집합 함수
- 분기 함수
m.get("username").isNull()
10.3.14 Criteria 메타 모델 API
m.get("age")
같이 문자를 사용하면 문제가 발생할 수 있음, 완전한 코드로 작성하기 위해 사용메타 모델 API 적용 전
cq.select(m) .where(cb.gt(m.<Integer>get("username"), 20)) .orderBy(cb.desc(m.get("age")));
메타 모델 API 적용 후
cq.select(m) .where(cb.gt(m.get(Member_.age), 20)) .orderBy(cb.desc(m.get(Member_.age)));
- Member_ 클래스가 메타 모델 클래스를 의미
- 개발자가 직접 입력하는 것이 아니라 코드 자동 생성기가 메타 모델 클래스를 생성
- 코드 생성기는 모든 엔티티 클래스를 찾아 "엔티티명_.java"를 생성
- 코드 생성기 설정
- 메이븐이나 엔트, 그래들 같은 빌드 도구를 사용해서 실행
MAVEN 설정
<dependencies> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-jpamodelgen</artifactId> <version>1.3.0.Final</version> </dependency> </dependencies> <build> <plugins> <plugin> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.6</source> <target>1.6</target> <compilerArguments> <processor>org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor</processor> </compilerArguments> </configuration> </plugin> </plugins> </build>
'개발서적 > 자바 ORM 표준 JPA' 카테고리의 다른 글
[자바 ORM 표준 JPA 프로그래밍] 10.5 네이티브 SQL (0) | 2021.09.13 |
---|---|
[자바 ORM 표준 JPA 프로그래밍] 10.4 QueryDSL (0) | 2021.09.13 |
[자바 ORM 표준 JPA 프로그래밍] 10.2 JPQL (0) | 2021.09.13 |
[자바 ORM 표준 JPA 프로그래밍] 10.1 객체체지향 쿼리 소개 (0) | 2021.09.13 |
[자바 ORM 표준 JPA 프로그래밍] 9.3~5 값 타입과 불변 객체, 값 비교, 값 타입 컬렉션 (0) | 2021.09.13 |