본문 바로가기
Framework and Tool/JPA

JPA - 객체지향 쿼리 언어 - Criteria 쿼리 생성

by ocwokocw 2021. 8. 5.

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

- Criteria 쿼리 생성

Criteria 를 사용하려면 CriteriaBuilder.createQuery() 메소드로 Criteria 쿼리를 생성하면 된다. CriteriaBuilder 인터페이스의 메소드 시그니처는 아래와 같다.

 

public interface CriteriaBuilder {

    /**
     *  Create a <code>CriteriaQuery</code> object.
     *  @return criteria query object
     */
    CriteriaQuery<Object> createQuery();

    /**
     *  Create a <code>CriteriaQuery</code> object with the specified result 
     *  type.
     *  @param resultClass  type of the query result
     *  @return criteria query object
     */
    <T> CriteriaQuery<T> createQuery(Class<T> resultClass);

    /**
     *  Create a <code>CriteriaQuery</code> object that returns a tuple of 
     *  objects as its result.
     *  @return criteria query object
     */
    CriteriaQuery<Tuple> createTupleQuery();

 

위의 3 개의 메소드가 구성요소의 전부는 아니다. 메소드 시그니처를 살펴보면 파라미터를 이용하여 쿼리 결과의 반환 타입을 지정할 수 있다.

 

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

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

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

cq.select(roots);

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

 

CriteriaBuilder 와 엔티티 매니저 둘다 메소드명이 createQuery 인데 헷갈리므로 의미를 잘 생각하면서 코드를 읽어야 한다. CriteriaBuilder 의 createQuery 는 CriteriaQuery 를 반환한다. 하지만 엔티티 매니저의 createQuery 는 TypedQuery 를 반환한다. 위 예제에서는 체이닝 메소드 방식으로 곧바로 getResultList() 를 호출하여 더 헷갈리는 면도 있다.

 

CriteriaBuilder 의 createQuery 를 이용하여 CriteriaQuery 생성시 Member.class 를 반환 타입으로 지정하면, 엔티티 매니저의 createQuery 메소드 시그니처는 아래와 같기 때문에 반환형을 지정하지 않아도 된다.

 

public <T> TypedQuery<T> createQuery(CriteriaQuery<T> criteriaQuery);

- Object 형 반환

만약 반환타입을 지정할 수 없거나 둘 이상이면 Object 형으로 반환받으면 된다.

 

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

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

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

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

 

cq.from 의 인자에 Object.class 를 넘기면 안된다. 반환타입인 select 를 Object 형으로 지정하는것이지 from 절을 Object 로 정하는것이 아니다. JPQL 의 from 절은 엔티티여야 한다.

 

그리고 어차피 from 의 인자를 Object 로 지정할 수 도 없다. 아래와 같이 하이버네이트에서 validation 을 하기 때문에 엔티티형을 인자로 넘겨야 한다.

 

public <X> Root<X> from(Class<X> entityClass) {
	EntityType<X> entityType = criteriaBuilder.getEntityManagerFactory()
			.getMetamodel()
			.entity( entityClass );
	if ( entityType == null ) {
		throw new IllegalArgumentException( entityClass + " is not an entity" );
	}
	return from( entityType );
}

- Object[] 형 반환

만약 반환 타입이 둘 이상일때는 어떻게 해야할까? 센스가 좀 있는 편이라면 앞에서 JPQL 프로젝션에서 Object[] 형으로 지정한것이 떠오를것이다. 

 

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

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

Root<Member> roots = cq.from(Member.class);
roots.join("orders");

cq.multiselect(roots);

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

 

아직 criteria join 을 배우지는 않았지만 multiselect 를 사용하여 Object[] 반환타입을 보여주기 위해 사용하였다. join 을 하지 않으면 Member 타입을 Object[] 형으로 캐스팅할 수 없기 때문에 에러가 발생한다.


- Tuple 형 반환

마지막으로 반환 타입 중 Tuple 에 대해 알아보도록 하자. Mybatis 에서 Query 결과 맵핑을 할 때 객체가 아니라 Map 을 사용하는 프로젝트도 있다. 

 

물론 Map 을 사용하면 내부에 어떤값이 들어있을지도 모르고 Type 도 불분명한 점도 있는 등 단점이 있다. 그래서 소위 최신기술 스택을 열심히 공부하는 힙한 개발자들이 Map 사용 방식을 대차게 까기도 한다. 물론 나도 단점이 많다고 생각한다.

 

하지만 프로젝트에서 나머지 팀원들이 자바 객체를 만들기가 귀찮아서 Map 을 표준으로 잡아 달라고 바락바락 우기면 답이 없다. 우리는 학교과제가 아니라 돈을 받고 기한내에 완성을 해주는 프로이기 때문에 팀원을 설득할 자신이 없거나 팀원이 무조건 Map 을 사용하자고 하면 기꺼이 그렇게 해야 한다. 

 

또한 필드에서 절대적으로 좋거나 나쁜것은 없다. 자본대비 아웃풋도 고려할 줄 알아야 한다. 사용할 일이 적더라도 숙지는 하고 있도록 하자.

 

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

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

Root<Member> roots = cq.from(Member.class);
cq.multiselect(
	roots.get("name").alias("userName"),
	roots.get("id").alias("userId"));

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

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

 

위의 코드를 실행하면 아래와 같은 SQL 이 생성된다.

 

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_

 

multiselect 에서 alias 를 준다고 해서 실제 SQL 의 alias 로 지정되는것은 아님을 알 수 있다.

 

사용법은 그렇게 어렵지는 않다. 위의 코드를 읽어보면 alias 로 지정한 명을 Map 처럼 꺼내쓰기만 하면 된다. 튜플의 자세한 사용법은 이후에 알아본다.

댓글