15장. Criteria 질의들

Hibernate는 직관적인, 확장 가능한 criteria query API를 특징 짓는다.

15.1. Criteria 인스턴스 생성하기

org.hibernate.Criteria인터페이스는 특정 영속 클래스에 대한 질의를 표현한다. SessionCriteria 인스턴스들에 대한 팩토리이다.

Criteria crit = sess.createCriteria(Cat.class);
crit.setMaxResults(50);
List cats = crit.list();

15.2. 결과 셋 제한하기

개별적인 질의 기준은 org.hibernate.criterion.Criterion 인터페이스의 인스턴스이다. org.hibernate.criterion.Restrictions 클래스는 어떤 미리 만들어진 Criterion 타입들을 얻는 팩토리 메소드들을 정의한다.

List cats = sess.createCriteria(Cat.class)
    .add( Restrictions.like("name", "Fritz%") )
    .add( Restrictions.between("weight", minWeight, maxWeight) )
    .list();

제한들은 논리적으로 그룹지워질 수도 있다.

List cats = sess.createCriteria(Cat.class)
    .add( Restrictions.like("name", "Fritz%") )
    .add( Restrictions.or(
        Restrictions.eq( "age", new Integer(0) ),
        Restrictions.isNull("age")
    ) )
    .list();
List cats = sess.createCriteria(Cat.class)
    .add( Restrictions.in( "name", new String[] { "Fritz", "Izi", "Pk" } ) )
    .add( Restrictions.disjunction()
        .add( Restrictions.isNull("age") )
        .add( Restrictions.eq("age", new Integer(0) ) )
        .add( Restrictions.eq("age", new Integer(1) ) )
        .add( Restrictions.eq("age", new Integer(2) ) )
    ) )
    .list();

미리 만들어진 criterion 타입들(Restrictions 서브클래스들)의 영역이 꽤 존재하지만, 특히 유용한 것은 당신으로 하여금 SQL을 직접 지정하도록 해준다.

List cats = sess.createCriteria(Cat.class)
    .add( Restrictions.sqlRestriction("lower({alias}.name) like lower(?)", "Fritz%", Hibernate.STRING) )
    .list();

질의된 엔티티의 행 alias에 의해 대체된 {alias} placeholder.

criterion을 얻는 대안적인 접근법은 Property 인스턴스로부터 그것을 얻는 것이다. 당신은 Property.forName()을 호출하여 Property를 생성시킬 수 있다.

Property age = Property.forName("age");
List cats = sess.createCriteria(Cat.class)
    .add( Restrictions.disjunction()
        .add( age.isNull() )
        .add( age.eq( new Integer(0) ) )
        .add( age.eq( new Integer(1) ) )
        .add( age.eq( new Integer(2) ) )
    ) )
    .add( Property.forName("name").in( new String[] { "Fritz", "Izi", "Pk" } ) )
    .list();

15.3. 결과들을 순서지우기(ordering)

당신은 org.hibernate.criterion.Order를 사용하여 결과들을 순서(ordering)지울 수 있다.

List cats = sess.createCriteria(Cat.class)
    .add( Restrictions.like("name", "F%")
    .addOrder( Order.asc("name") )
    .addOrder( Order.desc("age") )
    .setMaxResults(50)
    .list();
List cats = sess.createCriteria(Cat.class)
    .add( Property.forName("name").like("F%") )
    .addOrder( Property.forName("name").asc() )
    .addOrder( Property.forName("age").desc() )
    .setMaxResults(50)
    .list();

15.4. 연관들

당신은 createCriteria()를 사용하여 연관들을 네비게이트함으로써 관계된 엔티티들에 대한 컨스트레인트들을 쉽게 지정할 수 있다.

List cats = sess.createCriteria(Cat.class)
    .add( Restrictions.like("name", "F%") )
    .createCriteria("kittens")
        .add( Restrictions.like("name", "F%") )
    .list();

두 번째 createCriteria()Criteria의 새로운 인스턴스를 반환하며, 그것은 kittens 콜렉션의 요소들을 참조한다는 점을 노트하라.

다음 대체 형식은 어떤 환경들에서 유용하다.

List cats = sess.createCriteria(Cat.class)
    .createAlias("kittens", "kt")
    .createAlias("mate", "mt")
    .add( Restrictions.eqProperty("kt.name", "mt.name") )
    .list();

(createAlias()Criteria의 새로운 인스턴스를 생성시키지 않는다.)

앞의 두 개의 질의들에 의해 반환된 Cat 인스턴스들에 의해 보관된 kittens 콜렉션들은 criteria에 의해 사전-필터링되지 않는다는 점을 노트하라! 만일 당신이 criteria(기준)과 일치하는 고양이 새끼들을 단지 검색하고자 원할 경우, 당신은 하나의 ResultTransformer를 사용해야 한다.

List cats = sess.createCriteria(Cat.class)
    .createCriteria("kittens", "kt")
        .add( Restrictions.eq("name", "F%") )
    .setResultTransformer(Criteria.ALIAS_TO_ENTITY_MAP)
    .list();
Iterator iter = cats.iterator();
while ( iter.hasNext() ) {
    Map map = (Map) iter.next();
    Cat cat = (Cat) map.get(Criteria.ROOT_ALIAS);
    Cat kitten = (Cat) map.get("kt");
}

15.5. 동적인 연관 페칭

당신은 setFetchMode()를 사용하여 실행 시에 연관 페칭 의미를 지정할 수 있다.

List cats = sess.createCriteria(Cat.class)
    .add( Restrictions.like("name", "Fritz%") )
    .setFetchMode("mate", FetchMode.EAGER)
    .setFetchMode("kittens", FetchMode.EAGER)
    .list();

이 질의는 outer 조인으로 matekittens 모두를 페치할 것이다. 추가 정보는 19.1절. “페칭 방도들”을 보라.

15.6. 예제 질의들

org.hibernate.criterion.Example 클래스는 주어진 인스턴스로부터 질의 기준(criterion)을 구조화 시키는 것을 당신에게 허용해준다.

Cat cat = new Cat();
cat.setSex('F');
cat.setColor(Color.BLACK);
List results = session.createCriteria(Cat.class)
    .add( Example.create(cat) )
    .list();

버전 프로퍼티들, 식별자들, 연관관계들이 무시된다. 디폴트로 null 값 프로퍼티들이 제외된다.

당신은 Example이 적용되는 방법을 조정할 수 있다.

Example example = Example.create(cat)
    .excludeZeroes()           //exclude zero valued properties
    .excludeProperty("color")  //exclude the property named "color"
    .ignoreCase()              //perform case insensitive string comparisons
    .enableLike();             //use like for string comparisons
List results = session.createCriteria(Cat.class)
    .add(example)
    .list();

당신은 연관된 객체들에 대한 criteria(기준)을 위치지우는데 examples를 사용할 수 있다.

List results = session.createCriteria(Cat.class)
    .add( Example.create(cat) )
    .createCriteria("mate")
        .add( Example.create( cat.getMate() ) )
    .list();

15.7. Projections, aggregation 그리고 grouping

org.hibernate.criterion.Projections 클래스는 Projection 인스턴스들에 대한 팩토리이다. 우리는 setProjection()을 호출하여 하나의 질의에 projection(투사,투영)을 적용시킨다.

List results = session.createCriteria(Cat.class)
    .setProjection( Projections.rowCount() )
    .add( Restrictions.eq("color", Color.BLACK) )
    .list();
List results = session.createCriteria(Cat.class)
    .setProjection( Projections.projectionList()
        .add( Projections.rowCount() )
        .add( Projections.avg("weight") )
        .add( Projections.max("weight") )
        .add( Projections.groupProperty("color") )
    )
    .list();

criteria 질의 내에서는 명시적인 "group by"가 필수적이지 않다. 어떤 projection 타입들은 grouping projections들이게끔 정의되고, 그것은 또한 SQL group by 절 속에 나타난다.

alias는 선택적으로 projection에 할당될 수 있어서, 투사된(projected) 값은 제한(restriction)들 또는 ordering들 내에서 참조될 수 있다. 다음은 이것을 행하는 두 개의 다른 방법들이다:

List results = session.createCriteria(Cat.class)
    .setProjection( Projections.alias( Projections.groupProperty("color"), "colr" ) )
    .addOrder( Order.asc("colr") )
    .list();
List results = session.createCriteria(Cat.class)
    .setProjection( Projections.groupProperty("color").as("colr") )
    .addOrder( Order.asc("colr") )
    .list();

alias() 메소드와 as() 메소드는 또 다른 alias 된 Projection의 인스턴스 내에 하나의 projection 인스턴스를 간단하게 포장한다. 지름길로서, 당신이 projection을 projection 리스트에 추가할 때 당신은 alias를 할당할 수 있다:

List results = session.createCriteria(Cat.class)
    .setProjection( Projections.projectionList()
        .add( Projections.rowCount(), "catCountByColor" )
        .add( Projections.avg("weight"), "avgWeight" )
        .add( Projections.max("weight"), "maxWeight" )
        .add( Projections.groupProperty("color"), "color" )
    )
    .addOrder( Order.desc("catCountByColor") )
    .addOrder( Order.desc("avgWeight") )
    .list();
List results = session.createCriteria(Domestic.class, "cat")
    .createAlias("kittens", "kit")
    .setProjection( Projections.projectionList()
        .add( Projections.property("cat.name"), "catName" )
        .add( Projections.property("kit.name"), "kitName" )
    )
    .addOrder( Order.asc("catName") )
    .addOrder( Order.asc("kitName") )
    .list();

당신은 또한 projection들을 표현하는데 Property.forName()을 사용할 수 있다:

List results = session.createCriteria(Cat.class)
    .setProjection( Property.forName("name") )
    .add( Property.forName("color").eq(Color.BLACK) )
    .list();
List results = session.createCriteria(Cat.class)
    .setProjection( Projections.projectionList()
        .add( Projections.rowCount().as("catCountByColor") )
        .add( Property.forName("weight").avg().as("avgWeight") )
        .add( Property.forName("weight").max().as("maxWeight") )
        .add( Property.forName("color").group().as("color" )
    )
    .addOrder( Order.desc("catCountByColor") )
    .addOrder( Order.desc("avgWeight") )
    .list();

15.8. Detached 질의들과 서브질의들

DetachedCriteria 클래스는 당신에게 세션 영역의 외부에서 질의를 생성시키도록 하고, 그런 다음 나중에 어떤 임의의 Session을 사용하여 그것을 실행하도록 한다.

DetachedCriteria query = DetachedCriteria.forClass(Cat.class)
    .add( Property.forName("sex").eq('F') );
    
Session session = ....;
Transaction txn = session.beginTransaction();
List results = query.getExecutableCriteria(session).setMaxResults(100).list();
txn.commit();
session.close();

DetachedCriteria는 또한 서브질의를 표현하는데 사용된다. 서브질의들을 포함하는 Criterion 인스턴스들은 Subqueries 또는 Property를 통해 얻어질 수 있다.

DetachedCriteria avgWeight = DetachedCriteria.forClass(Cat.class)
    .setProjection( Property.forName("weight").avg() );
session.createCriteria(Cat.class)
    .add( Property.forName("weight).gt(avgWeight) )
    .list();
DetachedCriteria weights = DetachedCriteria.forClass(Cat.class)
    .setProjection( Property.forName("weight") );
session.createCriteria(Cat.class)
    .add( Subqueries.geAll("weight", weights) )
    .list();

심지어 상관관계 지워진 서브질의들이 가능하다:

DetachedCriteria avgWeightForSex = DetachedCriteria.forClass(Cat.class, "cat2")
    .setProjection( Property.forName("weight").avg() )
    .add( Property.forName("cat2.sex").eqProperty("cat.sex") );
session.createCriteria(Cat.class, "cat")
    .add( Property.forName("weight).gt(avgWeightForSex) )
    .list();

15.9. natural 식별자에 의한 질의들

대부분의 질의들에서, criteria 질의들을 포함하여, 질의 캐시는 매우 효율적이지 않다. 왜냐하면 질의 캐시 비유효성이 너무 자주 발생하기 때문이다. 하지만, 우리가 캐시 비유효성 알고리즘을 최적화 시킬 수 있는 한 가지 특별한 종류의 질의가 존재한다: 상수 natural 키에 의한 룩업. 몇몇 어플리케이션들에서, 이런 종류의 질의가 자주 발생한다. criteria API는 이 쓰임새를 위한 특별한 설비를 제공한다.

첫번째로 당신은 <natural-id>를 사용하여 당신의 엔티티에 대한 natural 키를 매핑 시켜야 하고, second-level 캐시 사용을 가능하게 해야 한다.

<class name="User">
    <cache usage="read-write"/>
    <id name="id">
        <generator class="increment"/>
    </id>
    <natural-id>
        <property name="name"/>
        <property name="org"/>
    </natural-id>
    <property name="password"/>
</class>

이 기능은 가변성 있는 natural 키들을 가진 엔티티들의 용도로 고안되어 있지 않음을 노트하라.

다음으로 , Hibernate 질의 캐시를 사용 가능하도록 하라.

이제 Restrictions.naturalId()는 캐시 알고리즘을 보다 효율적으로 사용할 수 있도록 우리에게 허용해준다.

session.createCriteria(User.class)
    .add( Restrictions.naturalId()
        .set("name", "gavin")
        .set("org", "hb") 
    ).setCacheable(true)
    .uniqueResult();