본문 바로가기
Framework and Tool/JPA

JPA - 객체지향 쿼리 언어 - Criteria 조회

by ocwokocw 2021. 8. 8.

- 참조: 자바 ORM 표준 JPA 프로그래밍

- 조회(select & multiselect)

Criteria 에서 SELECT 절을 만드는 함수는 아래와 같다.

 

/**
 * The <code>CriteriaQuery</code> interface defines functionality that is specific 
 * to top-level queries.
 *
 * @param <T>  the type of the defined result
 *
 * @since 2.0
 */
public interface CriteriaQuery<T> extends AbstractQuery<T> {
	
    CriteriaQuery<T> select(Selection<? extends T> selection);

    CriteriaQuery<T> multiselect(Selection<?>... selections);

    CriteriaQuery<T> multiselect(List<Selection<?>> selectionList);
	
	...........

 

조회 대상을 한 건을 지정할 것이냐 혹은 여러 건을 지정할 것이냐에 따라 select 혹은 multiselect 를 사용하면 된다.

 

//Criteria query builder
CriteriaBuilder cb = em.getCriteriaBuilder();

//Criteria 생성, 반환 타입 지정
CriteriaQuery<Member> cq = cb.createQuery(Member.class);

Root<Member> m = cq.from(Member.class);
cq.select(m);

List<Member> members = em.createQuery(cq)
	.getResultList();

members.forEach(member -> {
	System.out.println("member's name: " + member.getName());
});

 

위의 예제는 JPQL "select m from Member m" 에서 Member 엔티티 한 건을 조회하는 select 예제이다. 만약 "select m.name, m.id from Member m" 과 같이 select 절에서 여러 건을 조회하고 싶다면 아래와 같이 multiselect 를 이용해야 한다.

 

//Criteria query builder
CriteriaBuilder cb = em.getCriteriaBuilder();

//Criteria 생성, 반환 타입 지정
CriteriaQuery<Object[]> cq = cb.createQuery(Object[].class);

Root<Member> m = cq.from(Member.class);
cq.multiselect(m.get("name"), m.get("id"));

List<Object[]> members = em.createQuery(cq)
	.getResultList();

members.forEach(member -> {
	System.out.println("member's name: " + member[0]);
	System.out.println("member's id: " + member[1]);
});

 

여러 건에 대해 다른 맵핑방법은 아직 배우기 전이므로 CriteriaQuery 의 반환형을 Object[] 로 지정해주었다. cq.from 는 앞에서 언급했듯이 JPQL 에서 from 절에 해당하므로 from Member 와 같이 엔티티형이어야 한다. multiselect 함수 에서는 조회하려고 하는 대상들을 지정한다.

 

Hibernate: 
    /* select
        generatedAlias0.name,
        generatedAlias0.id 
    from
        Member as generatedAlias0 */ select
            member0_.name as col_0_0_,
            member0_.member_id as col_1_0_ 
        from
            member member0_
member's name: Name#1
member's id: 1
member's name: Name#2
member's id: 2
member's name: Name#3
member's id: 3
member's name: Name#4
member's id: 4

 

수행된 SQL 과 출력결과는 위와 같다. select 절에 조회대상이 잘 기술되었다.


- DISTINCT

Criteria 는 JPQL 과 마찬가지로 DISTINCT 기능도 제공한다. select 후 distinct(true)를 추가해주면 JPQL 의 DISTINCT 기능을 이용할 수 있다. DISTINCT 기능 테스트를 위해 member2, 3, 4 의 이름을 Name#2 로 변경해주자.

 

Member member1 = new Member();

member1.setName("Name#1");
em.persist(member1);

Member member2 = new Member();

member2.setName("Name#2");
em.persist(member2);

Member member3 = new Member();

member3.setName("Name#2");
em.persist(member3);

Member member4 = new Member();

member4.setName("Name#2");
em.persist(member4);

.....................

//Criteria query builder
CriteriaBuilder cb = em.getCriteriaBuilder();

//Criteria 생성, 반환 타입 지정
CriteriaQuery<Object> cq = cb.createQuery();

Root<Member> m = cq.from(Member.class);
cq.select(m.get("name")).distinct(true);

List<Object> members = em.createQuery(cq)
	.getResultList();

members.forEach(member -> {
	System.out.println("member's name: " + member);
});

 

criteria 부분 코드에서 select 뒤에 distinct(true) 가 추가된것을 볼 수 있다. 위 예제를 실행하면 SQL 이 아래처럼 변환되어 수행된다.

 

Hibernate: 
    /* select
        distinct generatedAlias0.name 
    from
        Member as generatedAlias0 */ select
            distinct member0_.name as col_0_0_ 
        from
            member member0_
member's name: Name#1
member's name: Name#2

 

distinct(true) 를 false 로 변환하여 수행하면 Name#2 가 중복되어 나오는 모습을 확인할 수 있다.


- 프로젝션(NEW, construct)

JPQL 프로젝션에서 new 구문을 배웠었다. Object[] 로 반환형을 결과로 참조한 다음 또 다시 형변환을 해주는 번거로움을 없애고자 DTO 를 하나 추가하여 new 뒤에 패키지명을 포함한 생성자구문으로 이를 해결했었다. 기억을 되뇌일 겸 코드를 다시한번 살펴보자.

 

public class MemberDTO {

	private Long id;
	private String name;

..............

String projectionJpql = "select new com.example.demo.member.dto.MemberDTO(m.id, m.name) "
		+ "from Member m";

List<MemberDTO> membersInfo = em.createQuery(projectionJpql, MemberDTO.class)
		.getResultList();

membersInfo.forEach(System.out::println);

...........

Hibernate: 
    /* select
        new com.example.demo.member.dto.MemberDTO(m.id,
        m.name) 
    from
        Member m */ select
            member0_.member_id as col_0_0_,
            member0_.name as col_1_0_ 
        from
            member member0_
com.example.demo.member.dto.MemberDTO@2932e15f
com.example.demo.member.dto.MemberDTO@3d98729a
com.example.demo.member.dto.MemberDTO@2375a976
com.example.demo.member.dto.MemberDTO@4bc21e34

 

형변환 필요없이 깔끔하게 DTO 계층을 이용하여 Member 엔티티의 일부 정보를 다룰 수 있다. 참고로 DTO 계층이라는 말에 대해 더 깊게 이해를 하고 싶다면 로버트 C.마틴의 클린아키텍처나 에릭 에반스의 DDD 에 해당 부분을 참조하면 된다. Criteria 를 이용하여 위의 JPQL 예제와 같이 반환형을 DTO 로 다루어보자.

 

CriteriaBuilder cb = em.getCriteriaBuilder();

CriteriaQuery<MemberDTO> cq = cb.createQuery(MemberDTO.class);

Root<Member> m = cq.from(Member.class);
cq.select(cb.construct(MemberDTO.class, 
	m.get("id"), m.get("name")));

List<MemberDTO> members = em.createQuery(cq)
	.getResultList();

members.forEach(member -> {
	System.out.println("member's name: " + member.getName());
});

 

cq.select 의 인자가 주의깊게 살펴보자. CriteriaBuilder 의 constrcut 를 이용하여 프로젝션을 수행한다. 우선 반환형 클래스 타입을 주어야 한다. JPQL 은 String 기반이라 패키지명을 모두 지정해줘야했지만 criteria 는 코드 기반이므로 MemberDTO.class 와 같은 방식으로 작성하는게 가능해진다. 그리고 이어서 생성자 인자들을 지정해준다.

 

수행되는 결과는 JPQL 로 프로젝션 쿼리를 작성할때와 같다.

 

Hibernate: 
    /* select
        new com.example.demo.member.dto.MemberDTO(generatedAlias0.id,
        generatedAlias0.name) 
    from
        Member as generatedAlias0 */ select
            member0_.member_id as col_0_0_,
            member0_.name as col_1_0_ 
        from
            member member0_
member's name: Name#1
member's name: Name#2
member's name: Name#3
member's name: Name#4

- 튜플

Criteria 도 Map 과 같은 비슷한 튜플이라는 기능을 지원한다.

 

CriteriaBuilder cb = em.getCriteriaBuilder();

CriteriaQuery<Tuple> cq = cb.createTupleQuery();

Root<Member> m = cq.from(Member.class);

cq.multiselect(m.get("id").alias("memberId"),
		m.get("name").alias("memberName"));

List<Tuple> members = em.createQuery(cq)
	.getResultList();

members.forEach(member -> {
	System.out.println("member's name: " + member.get("memberName"));
});

 

CriteriaQuery 에서 반환형을 Tuple 로 지정한다. 그리고 multiselect 에서 튜플 별칭들을 지정해준다. Tuple 을 사용할 때에는 Map 처럼 alias 에서 지정한 문자열로 정보를 불러오면 된다.

 

Hibernate: 
    /* select
        generatedAlias0.id,
        generatedAlias0.name 
    from
        Member as generatedAlias0 */ select
            member0_.member_id as col_0_0_,
            member0_.name as col_1_0_ 
        from
            member member0_
member's name: Name#1
member's name: Name#2
member's name: Name#3
member's name: Name#4

 

당연히 튜플도 JPQL 의 select m from Member m 처럼 엔티티를 조회할 수 있다.

 

cq.multiselect(
	m.alias("m"),
	m.get("id").alias("memberId"),
	m.get("name").alias("memberName"));
Hibernate: 
    /* select
        m,
        m.id,
        m.name 
    from
        Member as m */ select
            member0_.member_id as col_0_0_,
            member0_.member_id as col_1_0_,
            member0_.name as col_2_0_,
            member0_.member_id as member_i1_6_,
            member0_.insert_datetime as insert_d2_6_,
            member0_.update_datetime as update_d3_6_,
            member0_.city as city4_6_,
            member0_.street as street5_6_,
            member0_.zipcode as zipcode6_6_,
            member0_.name as name7_6_ 
        from
            member member0_
member's name: Name#1
member's name: Name#2
member's name: Name#3
member's name: Name#4

 

수행된 SQL 을 잘 보면 name 과 id 가 2번씩 select 절에 나타났고, Member 엔티티의 모든 속성이 나타난다.

댓글