Criteria Queries

Hibernate에서는 HQL에 익숙하지 못하거나 HQL 작성시 발생할 수 있는 오타로 인한 오류를 최소화 하기 위해 org.hibernate.Criteria API를 사용할 수 있도록 한다. Criteria API 호출을 통해 특정 객체에 대한 조회가 가능하고 org.hibernate.criterion.Restrictions API 호출을 통해 WHERE문에 해당하는 기본 조회 조건을 정의할 수 있다.

기본적인 사용 방법

Hibernate Criteria를 이용하여 특정 객체 정보에 대해 조회할 수 있다.

Case 1. Basic

다음은 하나의 테이블을 대상으로 Criteria를 이용하여 조회를 수행하는 예이다.
Criteria criteria = session.createCriteria(Country.class);
criteria.add(Restrictions.like("countryName", "", MatchMode.ANYWHERE));
criteria.addOrder(Order.asc("countryName"));
List countryList = criteria.list();
대상이 되는 테이블과 매핑되는 클래스로 Criteria를 생성하고 Restriction API를 호출해 WHERE조건에 해당하는 조건절을 정의할 수 있다. 위와 같이 정의 한 경우 WHERE Country.countryName like '%%'와 같은 조건절이 생성된다. 또한 addOrder()를 통해 order by절을 정의할 수 있다.
이와 같이 Criteria API를 이용할 경우 메소드를 통해 검색 조건을 정의하기 때문에 오타로 인한 오류를 최소화할 수 있게 된다. 조회 조건을 정의하기 위한 org.hibernate.criterion.Restrictions 는 eq, gt, ge, isNull 등을 비롯하여 다양한 API를 제공하고 있다. 보다 자세한 내용을 알기 위해서는 여기 를 참고하도록 한다.

Case 2. Join

Relation 관계에 놓여 있는 두개의 테이블을 대상으로 Hibernate Criteria(Inner Join)를 이용한 조회 작업을 수행할 수 있다.
Criteria movieCriteria = session.createCriteria(Movie.class);
Criteria categoryCriteria = movieCriteria.createCriteria("categories");
categoryCriteria.add(Restrictions.eq("categoryName", "Romantic"));
List movieList = movieCriteria.list();
위 코드에서는 Movie 클래스와 Relation 관계에 놓인 Category를 Join하기 위해 각 Movie 객체에 해당하는 Criteria에 Category 객체에 해당하는 Criteria를 생성하고 있다. 여기서는 Restrictions API를 사용하여 categoryName = 'Romantic'인 결과값을 찾게될 것이다.

또한, Relation 관계에 놓여 있는 두개의 테이블을 대상으로 Hibernate Criteria(Left Outer Join)을 이용한 조회 작업을 수행할 수 있다.
Criteria categoryCriteria = session.createCriteria(Category.class);
Criteria movieCriteria = categoryCriteria.createCriteria("movies",
		CriteriaSpecification.LEFT_JOIN);
categoryCriteria.addOrder(Order.asc("categoryName"));
categoryCriteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);

List categoryList = categoryCriteria.list();
Relation 관계에 있는 테이블의 Criteria를 생성할 때 CriteriaSpecification을 통해 LEFT_JOIN, RIGHT_JOIN 등을 명시할수 있다. 또한 Criteria.DISTINCT_ROOT_ENTITY를 사용하면 List에 중복 포함된 루트 개체를 제거할 수 있다. 위에서 설명된 코드들은 HibernateBasicCriteriaTest.java 에서 확인할 수 있다.

원하는 객체 형태로 전달

Criteria의 setResultTransformer 메소드를 사용하여 Criteria를 이용한 조회 결과를 별도 정의한 객체 형태로 전달받을 수 있다.

Case 1. 특정 객체 형태로 전달

Relation 관계에 놓여 있는 두개의 테이블을 대상으로 Criteria를 이용한 조회 결과를 특정 객체인 Movie 객체 형태로 전달받을 수 있다.
Criteria movieCriteria = session.createCriteria(Movie.class);
ProjectionList projectionList = Projections.projectionList();
projectionList.add(Projections.id().as("movieId"));
projectionList.add(Projections.property("title").as("title"));
projectionList.add(Projections.property("director").as("director"));
movieCriteria.setProjection(projectionList);
movieCriteria.setResultTransformer(new AliasToBeanResultTransformer(
		Movie.class));

Criteria categoryCriteria = movieCriteria.createCriteria("categories",
		"category");
Criteria countryCriteria = movieCriteria.createCriteria("country",
		"country");
categoryCriteria.add(Restrictions.eq("categoryName", "Romantic"));
countryCriteria.add(Restrictions.like("countryName", "",
		MatchMode.ANYWHERE));

List movieList = movieCriteria.list();
ProjectionList에 SELECT 절을 구성할 조회 대상 attribute들을 추가시키고 as() 메소드를 이용하여 각각의 attribute에 대한 alias를 정의할 수 있다. AliasToBeanResultTransformer 클래스를 사용하여 조회 결과의 형태를 Movie 클래스로 지정해준다. 따라서 위에서 정의한 Criteria 수행 결과는 Movie 객체의 List 형태가 될 것이다.
Movie movie1 = (Movie) movieList.get(0);
movie1.getTitle();
movie1.getDirector();

Case 2. Map 형태로 전달

Relation 관계에 놓여 있는 두개의 테이블을 대상으로 Criteria를 이용한 조회 결과를 Map 형태로 전달받을 수 있다.
Criteria movieCriteria = session.createCriteria(Movie.class);
ProjectionList projectionList = Projections.projectionList();
projectionList.add(Projections.id().as("movieId"));
projectionList.add(Projections.property("title").as("title"));
projectionList.add(Projections.property("director").as("director"));
movieCriteria.setProjection(projectionList);
movieCriteria.setResultTransformer(Criteria.ALIAS_TO_ENTITY_MAP);

Criteria categoryCriteria = movieCriteria.createCriteria("categories",
		"category");
Criteria countryCriteria = movieCriteria.createCriteria("country",
		"country");
categoryCriteria.add(Restrictions.eq("categoryName", "Romantic"));
countryCriteria.add(Restrictions.like("countryName", "",
		MatchMode.ANYWHERE));

List movieList = movieCriteria.list();
위에서 생성한 Criteria의 resultTransformer를 ALIAS_TO_ENTITY_MAP으로 지정하여 Map 형태의 결과값으로 전달 받을 수 있다. 이 때 조회 결과는 Map의 List 형태이며, alias로 정의한 movieId, title, director 등이 Map의 Key 값이 된다. 따라서 다음과 같이 Map의 Key 값을 통해 다음과 같이 결과값을 알아낼 수 있다.
Map movie1 = (Map) movieList.get(0);
movie1.get("title");
movie1.get("director");
위에서 언급된 코드는 HibernateCriteriaWithDefinedResult.java 에서 확인할 수 있다.

Pagination

Criteria를 이용하여 객체 조회시 페이징 처리된 결과를 얻기 위한 방법에 대해 알아본다. HQL을 사용한 Pagination과 마찬가지로 시작해야 하는 Row의 Number(FirstResult)와 조회 목록의 개수(MaxResult)를 정의함으로써, 페이징 처리를 할 수 있다. 사용 예는 다음과 같다.
Criteria criteria = session.createCriteria(Movie.class);
criteria.setFirstResult(1);
criteria.setMaxResults(2);
List movieList = criteria.list();
위와 같이 정의할 경우 Hibernate Configuration 파일(hibernate.cfg.xml)에 정의된 hibernate.dialect 속성에 따라 각각의 DB에 맞는 SQL을 생성한다. 이는 Pagination을 할 때 모든 데이터를 읽은 후 해당 페이지에 속한 데이터 갯수를 결과값으로 전달하는 것이 아니라 조회해야할 데이터 즉, 해당 페이지에 속한 갯수만큼의 데이터만 읽어오게 된다. 다음은 hibernate.dialect를 HSQL DB로 정의하였을 때 페이징 처리가 되어 수행된 쿼리문이다.
select limit 1 2 this_.MOVIE_ID as MOVIE1_3_0_, this_.COUNTRY_CODE as COUNTRY2_3_0_, this_.TITLE 
as TITLE3_0_, this_.DIRECTOR as DIRECTOR3_0_, this_.RELEASE_DATE as RELEASE5_3_0_ from PUBLIC.MOVIE 
this_ 
위의 코드에서 정의한 것처럼 첫번째로 조회해야 할 항목의 번호를 1, 조회 항목의 전체 개수를 2로 정의하였으므로 Hibernate에서는 HSQL DB의 특성에 맞게 'limit 1 2'가 추가된 SQL을 실행하여 페이징 처리를 수행하였다. 위의 코드는 HibernateCriteriaPagingTest.java 에서 확인할 수 있다.

Resources

  • 다운로드
  • 샘플 테스트 코드를 포함하고 있는 anyframe-hibernatetest-src.zip 파일을 다운받은 후, 테스트 환경 설정 을 참조하여 위에서 제시한 예제 코드 (src/test/java 폴더의 anyframe.core.hibernate.criteria 패키지와 anyframe.core.hibernate.paging 패키지에 속한 *Test.java)를 실행해 볼 수 있다.
    Name
    Download
    anyframe-hibernatetest-src.zip
    Download