HQLCriteria (仮)

某巨大掲示板でのやりとりでわかったことなのですが,HibernateCriteria って,結構出来ないことがあるようです.
その一つは SELECT 句を指定できないこと.
Criteria を使う場合には,

    session.createCriteria(Model.class)

のように永続クラスを指定して Criteriaインスタンスを作成します.
ここで指定した永続クラスを取得するのがこの Criteria の役割です.
そのため,SELECT 句を指定して一部のプロパティを取得することや,COUNT などの集計関数を使うことは出来ません.
おそらくは同じ理由で GROUP BY を指定することも出来ません.GROUP BY を適用すると,その結果はもはや特定の永続オブジェクトの状態とはいえないので.


そんなわけで (どんなわけで?),集計関数を適用したい場合は HQL を使うというのが Hibernate 流ということのようです.
でも,それじゃ不便な場合もありますよね.
個人的には Criteria が使える状況でも HQL を使ったりするので今まで気になったことはなかったのですが,問い合せ条件などが動的に変わる場合には,HQL 文字列を組み立てるよりも Criteria でやりたいかな.
でもでも,画面から入力された項目を検索条件として該当件数を表示する,という場合などでは Criteria は使えないわけで...


そんなわけで (どんなわけで?),ちょっと作ってみました.その名も HQLCriteria
Hibernate 本来の Criteria は直接 SQL を生成するようですが,HQLCriteria はその名の通り,HQL を作成します.なぜならその方が簡単そうだったから.(^^;
でもこれ,日記に掲載するにはクラスの数がずいぶん増えちゃったので,ソースのアーカイブを以下に置きました.


http://www.wikiroom.com/koichik/?plugin=attach&pcmd=open&file=HQLCriteria.zip&refer=Hibernate%20%C6%FE%CC%E7%B5%AD


なんせ,ちょっとした思いついてちゃちゃっとコード書いただけなので,機能・品質ともに箸にも棒にもという感じなのですが,そのあたりはご容赦を.


HQLCriteria は次のように使います.
まずは単純な例から.

            Iterator it = new HQLCriteriaImpl(session)
                .from(Model.class)
                .list().iterator();

HQLCriteria (の実装クラス) の from() メソッドで対象となる永続クラスを指定します.
これを実行すると,次の HQL が作成されます.

from study.Model

SELECT 句を指定することも出来ます.まずはプロパティを取得する場合.

            Iterator it = new HQLCriteriaImpl(session)
                .select("model.name")
                .from(Model.class, "model")
                .list().iterator();

from() メソッドでエイリアスを指定していることに注意.
この場合の HQL.

from study.Model model

そして集計関数の例.

            Integer count = (Integer) new HQLCriteriaImpl(session)
                .select(Aggregate.count())
                .from(Model.class, "model")
                .uniqueResult();

結果が単一行の場合は list() ではなくて uniqueResult() が使えます.
この場合の HQL.

select count(*) from study.Model model

結合する場合はこんな感じ.

            Iterator it = new HQLCriteriaImpl(session)
                .from(Join.root(Model.class, "model").inner("magazine", "mag"))
                .list().iterator();

この場合の HQL.

from study.Model model inner join model.magazine mag

HQL なので,FETCH JOIN も使えます.というか,明示的に指定しないと N + 1 になっちゃいます.

            Iterator it = new HQLCriteriaImpl(session)
                .from(Join.root(Model.class, "model").innerFetch("magazine", "mag"))
                .list().iterator();

この場合の HQL.

from study.Model model inner join fetch model.magazine mag

そして GROUP BY も.

            Iterator it = new HQLCriteriaImpl(session)
                .select(Aggregate.count())
                .from(Join.root(Model.class, "model").inner("magazine", "mag"))
                .groupBy("mag.name")
                .list().iterator();

この場合の HQL.

select count(*) from study.Model model inner join model.magazine mag group by mag.name


と,ここまではいい感じなのですが,WHERE 句と HAVING 句に関しては全然出来ていません.(^^;
最初は,HibernateExpressionCriterion をそのまま使えるんじゃないかと期待して始めたのですが,どうもそれは簡単ではないみたい.
となると,膨大な演算子を自前で用意しないといけないわけで,これはちょっとたいへん...
とりあえず,= だけ用意しました (苦笑).
WHERE 句を指定する場合の例.

            Integer count = (Integer) new HQLCriteriaImpl(session)
                .select(Aggregate.count())
                .from(Join.root(Model.class, "model").inner("magazine", "mag"))
                .where(Expression.eq("mag.name", "CanCam"))
                .uniqueResult();

この場合の HQL.

select count(*) from study.Model model inner join model.magazine mag where (mag.name = ?)

現状,パラメータの型の扱いがいい加減なので,心より恥じる結果になる場合があるかもしれません.
そして HAVING 句を指定した場合の例.

            Integer count = (Integer) new HQLCriteriaImpl(session)
                .select(Aggregate.count())
                .from(Join.root(Model.class, "model").inner("magazine", "mag"))
                .groupBy("mag.name")
                .having(Expression.eq(Aggregate.min("model.id"), new Integer(0)))
                .uniqueResult();

この場合の HQL.

select count(*) from study.Model model inner join model.magazine mag group by mag.name having (min(model.id) = ?)

各句は,複数指定することも出来ます.

            Iterator it = new HQLCriteriaImpl(session)
                .select("mag.name")
                .select(Aggregate.count())
                .select(Aggregate.avg("model.age"))
                .from(Join.root(Model.class, "model").inner("magazine", "mag"))
                .groupBy("mag.name")
                .list().iterator();

from() を複数記述すると,クロス結合になります.たぶん.一回も動かしてないけど.(^^;
where() および having() を複数記述すると,AND で接続します.OR が必要なら Expression.or を使います.というか作らないといけません (苦笑).


という感じなのですが,なんせ思いつきで作った程度のものなので,現状はかなりイマイチかも〜.
特にクラス名やメソッド名が HibernateCriteria と同じだったり違ってたりというのが相当イマイチかも〜.
まぁ,何かのヒントになればということで作っただけなので,細かい突っ込みはご容赦を.