Whiteship's Note


[하이버네이트 Criteria] 목록 사이즈 구하기

모하니?/Coding : 2010.05.27 10:47


난 편의상 이런 메서드들을 만들어 놓고 쓴다.

    private Session getSession() {
        return sessionFactory.getCurrentSession();
    }

    private Criteria getCriteriaOf(Class clazz){
        return getSession().createCriteria(clazz);
    }

이 두개가 있다고 했을 때.. Criteria API를 이용해서 목록 사이즈를 구하는 코드를 작성해보자.

    public int totalSize() {
        return getCriteriaOf(Code.class).list().size();
    }

캬.. 얼마나 명시적인가. 하지만 이러면 안된다. ㅠ.ㅠ.. 난 하이버가 좀 더 똑똑해져서 저렇게 짜더라도 알아서 count 쿼리를 만들어 주면 좋겠지만 그러지 않는다. 전부다 select 해온다. 로그를 보면..

Hibernate: select this_.id as id0_0_, this_.code as code0_0_, this_.descr as descr0_0_, this_.name as name0_0_ from Code this_

이렇다. 필요한건 사이즈 뿐인데 Code 테이블에 있는걸 전부다 가져온 셈이다. 크헉.. 따라서 목록 사이즈를 구할 때는 이런 쿼리를 쓰면 안된다.. 절대로; 그럼 어떻게 할까나..

    public int totalSize() {
        return (Integer)getCriteriaOf(Code.class)
            .setProjection(Projections.count("id"))
            .uniqueResult();
    }

이렇게 하면된다. Projections를 이용하면 count 말고도 max, min등을 사용할 수 있다. 코드가 좀 길어지고 Projection API에 적응해야 한다는 단점이 있지만 쿼리는 깔끔해진다.

Hibernate: select count(this_.id) as y0_ from Code this_

딱 내가 원하던 쿼리다. 하이버네이트를 쓸땐 이렇게 Criteria나 HQL이 생성해주는 SQL도 일일히 확인하는것이 좋다. 사실 일일히 확인하는 작업을 DBA가 해주면 좋겠지만 하이버네이트랑 친한 DBA가 있을 때의 이야기이고 그렇지 않다면 본인이 해야겠다. 아니면 하이버네이트를 마스터해서 어떤 쿼리가 생성되는지 달달달 꽤고 있다면 시간을 좀 단축 시킬 수 있을 것 같기도 하다.

하이버네이트를 마스터 하기 위해서는 조마간 나올 하이버네이트 번역서가 필수라는....앗..  광고를 하려고 시작한 글은 아니었는데;; ㅋㅋㅋ

ps1: Criteria를 사용해서 좀 더 간결한 코딩으로 가져오는 방법이 있으면 알고 싶다.
ps2: 반드시 Criteria를 써야 한다. 나중에 저 쿼리에 검색 조건이 임의로 추가될텐데 그럴때 Criteria가 빛을 발하기 때문이다. 난 동적쿼리를 SQL이나 HQL로 짜지 못하겠다. 짜증난다.
top

  1. Favicon of http://lckymn.com BlogIcon Kevin 2010.05.27 18:36 PERM. MOD/DEL REPLY

    본문과는 좀 다른 얘긴데요,
    최근에 나온 Hibernate에서도 JPA 2.0을 지원하기
    시작해서 관련된 얘길 잠깐... ^^;

    하이버네이트가 JPA 2.0 지원 시작하면서 바로 갈아타고
    JPQL도 전부 Typesafe query API 사용하는걸로 대체 했습니다만...
    이거 API가... 영...ㅡ_ㅡ; 코드 가독성을 떨어뜨리네요.
    typesafety 말고는 QL 언어보다 장점을 못 찾겠습니다.
    (뭐 사실 그걸 장점으로 내세운거긴 합니다만...ㅡ_ㅡ; )
    부가 코드만 늘어나고... 장점에 따라오는 단점이 좀...

    리턴 타입이야 generics 사용해서 Query 랑 EntityManager 확장해서
    어느정도 typesafe 하게 만들수 있다보니,
    JPA 2.0의 typesafe queries 는 리턴타입 이외에도
    parameter의 typesafety 를 보장해준다는거 정도밖에는 장점을
    못 느끼겠습니다. 이게 꽤 큰 장점이긴 하지만,
    그나마도 따로 @StaticMetamodel annotation을 마킹한 class를
    작성해야 가능하니... 없이 만들면 기존의 setParameter 와 별 다를바가...

    나름대로 필요한 객체들 묶어서 CriteriaManager 를 만들어서 쓰니 조금 편하긴한데
    이래도 가독성 높이려면 뭔가 한참은 부족하네요...ㅡ_ㅡ;

    암튼 그래서 Querydsl 을 사용해 볼까도 고민중이긴 합니다만...
    http://source.mysema.com/display/querydsl/Querydsl

    Favicon of http://whiteship.me BlogIcon 기선 2010.05.27 23:50 PERM MOD/DEL

    저도 JPA2로 갈아탈까 고민했었는데 아직 스프링 번들 저장소에 라이브러리가 뜨지도 않았고 스프링 3.0.2 의존성도 아직 JPA2로 넘어가질 않아서 일단 하이버 3.4 쓰고 있습니다.

    JPA2에 있는 Criteria랑 하이버에 있던 Criteria랑 사용하는게 많이 다른가요? 하이버 Criteria API랑 많이 다르지만 않다면 넘어가볼까 합니다.

    쿼리 자체가 복잡하면 Criteria로 표현하는 것도 복잡하지만 SQL도 어쩔 수 없이 복잡해지지 않을까 싶네요. 하지만 SQL에 익숙하신 분들은 정말 Criteria가 더 복잡해 보일 수도 있을 것 같아요. 본문에 있는 내용도 거의 머;; @_@;;

  2. Favicon of https://slothink.tistory.com BlogIcon 편현장 2010.06.30 00:59 신고 PERM. MOD/DEL REPLY

    Projections.rowCount() 나, Projections.distinctCount() 도 좋지요. 보통 이런건 자주 쓰는거랑 하이버네이트 공통 클래스에다가 넣고 사용하곤 하는데, Code.class 에 대한 클래스 정의도 Dao 클래스에서 추상화시키면 더 간단해질거 같습니다. 그나저나, 하이버네이트 번역서 오늘 받아서 13장 패치 부분을 다 읽었는데 정말 좋군요. 감사합니다..^^

    Favicon of http://whiteship.me BlogIcon 기선 2010.06.30 06:54 PERM MOD/DEL

    rowCount()가 있었군요!
    GenericDao에 적용해볼 만한 내용이군요.
    감사합니다!!

Write a comment.


하이버네이트 Criteria 검색 쿼리 골치



select this_.id as y0_ from Post this_ where this_.board_id=?

select this_.id as id1_0_, this_.author as author1_0_, this_.board_id as board7_1_0_, this_.contents as contents1_0_, this_.created as created1_0_, this_.title as title1_0_, this_.updated as updated1_0_
from Post this_
where this_.id in (?, ?, ?, ?, ?, ?) and  lcase(this_.title) like ? or lcase(this_.contents) like ?

===============================================

select this_.id as id1_0_, this_.author as author1_0_, this_.board_id as board7_1_0_, this_.contents as contents1_0_, this_.created as created1_0_, this_.title as title1_0_, this_.updated as updated1_0_
from Post this_
where this_.board_id=? and (lcase(this_.title) like ? or lcase(this_.contents) like ?)

================================================

select this_.id as id1_0_, this_.author as author1_0_, this_.board_id as board7_1_0_, this_.contents as contents1_0_, this_.created as created1_0_, this_.title as title1_0_, this_.updated as updated1_0_
from Post this_
where this_.id in
(select this0__.id as y0_ from Post this0__ where this0__.board_id=?)
and (lcase(this_.title) like ? or lcase(this_.contents) like ?)

=================================================

셋 다 똑같네.. 아.. 이런 ㅠ.ㅠ OTL... 뭐야 ㅠ.ㅠ 어떻게 짜야되지. 흠...

(lcase(this_.title) like ? or lcase(this_.contents) like ?)

이 부분이 잘못 됐나? title에 ?가 있거나 contents에 ?가 있는 것들 검색하려는데 분명 테스트 데이터에는 두 개가 나와야 되는데 계속 한 개만 나오네. 흠... 모르겠네... 모르겠어..

=====================
id | title       | contents
1  | keesun | toby
2  | toby      | toby
3  | toby      | keesun
=====================

이 상태에서 서브 쿼리나 in 뒤에 담겨있는 id는 전부 1, 2, 3이라고 치고... titke이 keesun이거나 contents가 keesun이 row를 갖다 달라는 거자나.

그럼 1번 row를 보면 title이 keesun이니까 맞자나. title이 keesun 이자나 contents는 아니고 그럼 일단 하나.
그리고 3번은 contents가 keesun이니까 맞고..

그럼 결과가 2개가 되야 하는데

결과는 1번만 가져오는... 이 상황은 ..;;; 어렵네~
top

Write a comment.


하이버네이트 Criteria 다루기 - 중복일까 아닐까

모하니?/Coding : 2008.07.30 13:31


    @Override
    public List<T> search(P params, OrderPage orderPage) {
        // total rowcount
        orderPage.setRowcount((Integer) (addRestrictions(
                getSession().createCriteria(this.persistentClass), params)
                .setProjection(Projections.rowCount()).uniqueResult()));

        // pages list
        Criteria c = addRestrictions(getSession().createCriteria(
                this.persistentClass), params);
        orderPage.applyPage(c);
        orderPage.applyOrder(c);

        return c.list();
    }

    /**
     * template method for search
     *
     * @param c
     * @param params
     * @return
     */
    protected Criteria addRestrictions(Criteria c, P params) {
        return c;
    }

위 코드에서 중복이 보이시나요? 안 보이신 다구요?

    @Override
   public List<T> search(P params, OrderPage orderPage) {
       // total rowcount
       orderPage.setRowcount((Integer) (addRestrictions(
               getSession().createCriteria(this.persistentClass), params)
               .setProjection(Projections.rowCount()).uniqueResult()));

       // pages list
       Criteria c = addRestrictions(getSession().createCriteria(
               this.persistentClass), params);
       orderPage.applyPage(c);
       orderPage.applyOrder(c);

       return c.list();
   }

어떤가요. 중복 이죠? 그러나..

Criteria c = addRestrictions(getSession().createCriteria(
               this.persistentClass), params);
orderPage.setRowcount((Integer) (c.setProjection(Projections.rowCount()).uniqueResult()));
orderPage.applyPage(c);
orderPage.applyOrder(c);

대강 이런 식으로 리팩터링 해보면 하이버네이트는 요상한 쿼리와 함께 에러를 뱉어냅니다.

전체 Row 갯수를 반환하는 Criteria(쿼리는 select count(*).. )이런식으로 시작)를 다시 Order와 Page 처리를 할 때 사용하면 이상한 쿼리(select count(*).. order by ... 이게 이상한 이유는 order by에서 사용한 컬럼이 group by에 있어야 하는데 groupd by를 정의한 적이 없거니와, 사실 두 번째 쿼리는 count(*)가 없어야 하는데 앞에서 만들어둔 Criteria에 이어 붙인 꼴이 되어서 이상해졌습니다.)가 되버립니다.

중복처럼 보이지만 제거하면 코드가 깨지는... 요상한 경우. 이거 어떻게 처리하는게 좋을까요? 전 요리 조리 해보다가 그냥 뒀습니다.
top

  1. 백가 2008.07.30 18:39 PERM. MOD/DEL REPLY

    pages list 를 먼저 구하고 total rowcount를 나중에 처리해도 똑같은 현상인가요?

    얼핏 보기에는 .setProjection(Projections.rowCount()).uniqueResult() 에서 무언가 c의 상태를 변화시킴으로 해서
    orderPage.applyPage(c);
    orderPage.applyOrder(c);
    에서 요구하는 c의 상태와는 다른 c 가 되는 모양으로 보입니다만.

    Favicon of http://whiteship.tistory.com BlogIcon 기선 2008.07.30 21:09 PERM MOD/DEL

    네.. 그렇게 해봤죠.ㅋㅋ 그렇게 해도 비슷한 HQL이 생성되서 에러가 납니다.

    applyPage와 applyOrder에서도 쿼리를 손보기 때문에, 결과는 같습니다.

Write a comment.


Criteria에서 Join하기

Hibernate/study : 2007.01.29 20:11


참조 : http://www.hibernate.org/hib_docs/v3/api/org/hibernate/Criteria.html

Member(1) --- (*)Messenger 의 관계에서 다음과 같이 데이타가 들어있습니다.

Member m1 --- Messenger msg1("seal", MSN)
Member m1 --- Messenger msg2("seal2", Skype)
Member m3 --- Messenger msg3("keesun", MSN)

이 때 "MSN 메신저가 있는 모든 멤버"를 가져 오려면 join을 해야 합니다. Criteria를 사용해서 Join을 하는 방법은 두 가지가 있습니다.

1. createCriteria() 메소드 사용하기.

    public void testCriteriaJoin1(){
        insertDatas();
        final KMessengerType messengerType = KMessengerType.MSN;

        Criteria c = s.createCriteria(KMember.class)
                      .createCriteria("messengers")
                        .add(Restrictions.eq("m_type", messengerType));

        assertEquals(2, c.list().size());
    }

2. createAlias() 메소드 사용하기.

    public void testCriteriaJoin2(){
        insertDatas();
        final KMessengerType messengerType = KMessengerType.MSN;

        Criteria c = s.createCriteria(KMember.class)
                      .createAlias("messengers", "msg")
                      .add(Restrictions.eq("msg.m_type", messengerType));

        assertEquals(2, c.list().size());
    }

여기까지만 봐서는 둘의 차이가 단순히 alias를 사용해야만 한다는 것 밖에 모르겠습니다.[각주:1]

"MSN 메신저를 가지고 이름이 keesun인 멤버"를 찾으려면 위 주건에 .add(Restriction.eq("name", "keesun")만 추가하면 됩니다. 이것을 맨 끝에 추가 해보면 둘의 차이를 알 수 있습니다.

1. createCriteria() 메소드를 사용.

    public void testCriteriaJoin1(){
        insertDatas();
        final KMessengerType messengerType = KMessengerType.MSN;

        Criteria c = s.createCriteria(KMember.class)
                      .createCriteria("messengers")
                          .add(Restrictions.eq("m_type", messengerType))
                          .add(Restrictions.eq("name", "keesun"));

        assertEquals(1, c.list().size());
    }
=> Messenger에 name이라는 컬럼이 없다면서 HQE[각주:2]가 발생합니다.

2. createAlias() 메소드를 사용.
   
public void testCriteriaJoin2(){
        insertDatas();
        final KMessengerType messengerType = KMessengerType.MSN;

        Criteria c = s.createCriteria(KMember.class)
                      .createAlias("messengers", "msg")
                      .add(Restrictions.eq("msg.m_type", messengerType))
                      .add(Restrictions.eq("name", "keesun"));

        assertEquals(1, c.list().size());
    }
=> 제대로 동작합니다.

createCriteria(String) 메소드 뒤에 붙은 add() 메소드 들은 해당 String 타입에 대항하는 컬럼에 참조하게 되고 createAlias(String, String)메소드 뒤에 붙은 add() 메소드들은 여전히 기본(this)이 상위에 있는 createCriteria(Class)에 있는 Class 타입을 나타냅니다.

1을 제대로 동작하게 하려면 추가한 문장을 s.createCriteria(KMember.class) 요것 바로 다음으로 이동시키면 됩니다.

2에서 좀더 명확하게 나타내려면 .add(Restrictions.eq("this.name", "keesun")); 이렇게 this를 추가해 주면 됩니다.

  1. 'msg.'을 빼고 실행하면 Member에 m_type이란 컬럼이 없다면서  Hibernate.QueryException이 발생합니다. [본문으로]
  2. Hibernate Query Exception [본문으로]

'Hibernate > study' 카테고리의 다른 글

The problem of graularity  (0) 2007.02.14
Custom Tag 만들기  (0) 2007.02.07
책을 샀습니다.  (0) 2007.02.04
DisplayTag1.1 예제 보기  (0) 2007.01.30
DisplayTag 써보기  (2) 2007.01.30
Criteria에서 Join하기  (0) 2007.01.29
숙제 2  (0) 2007.01.26
HibernateTemplate  (0) 2007.01.26
HQL과 @Entity에 있는 name 속성의 관계  (0) 2007.01.24
Fluent Interface  (2) 2007.01.24
DbUnit  (2) 2007.01.23
top

Write a comment.


8.3. Criteria 공부하기



특정 Member의 검색을 할 때 이름만 입력할 수도 있고 이메일만 입력할 수도 있습니다. 입력 할 수 있는 곳이 여러 곳이면 둘 다 입력하거나 둘 다 입력하지 않을 수도 있습니다. 이럴 때 입력을 하느냐 안하느냐에 따라 쿼리가 달라지는데요. 이런것을 Dynamic Query라고 하는것 같습니다.

HQL을 이용해서 이러한 쿼리를 다음과 같이 작성할 수 있습니다.
    public void testDynamicQueryByHQL(){
        insertDatas();
        String name = "s";
        String email = null;

        StringBuffer sb = new StringBuffer("from k_Member m where 1=1");
        if(!StringUtils.isEmpty(name)) sb.append(" and m.name like :name");
        if(!StringUtils.isEmpty(email)) sb.append(" and m.email like :email");
        q = s.createQuery(sb.toString());
        if(!StringUtils.isEmpty(name)) q.setParameter("name", "%" + name + "%");
        if(!StringUtils.isEmpty(email)) q.setParameter("email", "%" + email + "%");

        assertEquals(2, q.list().size());
    }

위와 같은 내용의 쿼리를 Criteria를 사용해서 작성하면 다음과 같이 간결해 집니다. 그리고 @Entity의 name속성에 지정해 준 값을 사용하는 것이 아니라 진짜 클래스 명을 사용해야 합니다.
    public void testDynamicQueryByCriteria(){
        insertDatas();
        String name = "s";
        String email = "keesun@os.net";

        Criteria c = s.createCriteria(KMember.class);
        if(!StringUtils.isEmpty(email)) c.add(Restrictions.eq("email", email));
        if(!StringUtils.isEmpty(name)) c.add(Restrictions.like("name", "%" + name + "%"));

        assertEquals(1, c.list().size());
    }

Criteria의 add 메소드는 인자로 Criterion을 넘겨 주어야 하는데요. Restrictions라는 팩토리를 사용해서 Criterion을 받아 오게 됩니다. Restrictions 클래스에는 쿼리에 덧붙일 수 있는 여러 메소드들이 있습니다.

'Hibernate > 주소록 만들기' 카테고리의 다른 글

9. Tag만들기  (0) 2007.02.12
8.4. 기능 구현  (0) 2007.01.25
8.3. Criteria 공부하기  (0) 2007.01.25
8.2. HQL 공부하기  (0) 2007.01.24
8.2.4. HQL 공부하기 - inner join  (0) 2007.01.24
8.2.3. HQL 공부하기 - order by절  (0) 2007.01.24
8.2.2. HQL 공부하기 - where절  (0) 2007.01.24
8.2.1. HQL 공부하기 - select절  (0) 2007.01.24
8.1. DbUnit 사용하기  (0) 2007.01.24
8. DAO 기능 구현하기  (4) 2007.01.23
7.3. 새로운 타입으로 맵핑하기.  (0) 2007.01.22
top

Write a comment.