Kuina-Dao 開発記 Criteria

Kuina-Dao はインタフェースに定義されたメソッドのシグネチャと実引数から動的に JPQL を作成して実行します.
でもでも,JPA には Criteria がないため,JPQL を組み立てるのが面倒です.
そんなわけで (どんなわけで?),Kuina-Dao のインフラとして Criteria を実装しました.


Kuina-Dao Criteria を使うには,まずは

import static org.seasar.kuina.dao.criteria.CriteriaOperations.*;

します.
Eclipse の場合は「Window」−「Preferences」から「Java」−「Code Style」−「Organize Import」を開いて「New Static...」で上のクラスを登録しておきます.
それでも Eclipse (3.1) は static import の扱いがイマイチで全然快適じゃないんですけどね.


ともあれ (JW),Kuina-Dao Criteria を使った一番簡単な問い合わせは次のようになります.

        List<Employee> list = select().from(Employee.class).getResultList(em);

スッキリしてて簡単でしょ? (^^;
このように select() に引数を指定しない場合は from() 句に指定したエンティティが選択されます.
これを明示的に指定すると

        List<Employee> list = select("employee").from(Employee.class).getResultList(em);

となります.
ちなみに,from() にクラスだけ指定しているので,エンティティクラス名を decapitalize したものが alias になります.
それも明示的に指定すると

        List<Employee> list = select("e").from(Employee.class, "e").getResultList(em);

となります.
select() は実は可変長引数を持つので,複数の選択リストを並べることができます.

        List<Object[]> list = select("e.name", "e.email").from(Employee.class, "e").getResultList(em);

結果リストから重複を除くには selectDistinct() を使います.

        List<String> list = selectDistinct("e.bloodType").from(Employee.class, "e").getResultList(em);


結合も簡単.
from() も可変長引数を受け取ります.その場合はクロス結合になります.

        List<Object[]> list = select().from(Employee.class, Department.class).getResultList(em);

可変長引数の from() で alias を指定するには alias() を使います.

        List<Object[]> list = select()
                .from(alias(Employee.class, "e"), alias(Department.class, "d"))
                .getResultList(em);

内部結合になると少し複雑な書き方になっちゃいます.

        List<Object[]> list = select()
                .from(join(Employee.class).inner("employee.belongTo").inner("belongTo.department"))
                .getResultList(em);

alias を使うとこう.

        List<Object[]> list = select()
                .from(join(Employee.class, "e").inner("e.belongTo", "b").inner("b.department", "d"))
                .getResultList(em);

いずれの場合も,結合した 3 つのエンティティの配列からなるリストが返されます.
FETCH JOIN も使えます.

        List<Employee> list = select()
                .from(join(Employee.class, "e").innerFetch("e.belongTo", "b").innerFetch("b.department", "d"))
                .getResultList(em);

この場合は Employee だけが返されます.
外部結合も同様.

        List<Product> list = selectDistinct()
                .from(join(Product.class, "p").leftFetch("p.sales"))
                .getResultList(em);

この場合は selectDistinct() しないと SQL の結果セットの行数と同じ長さのリストが返ってきます.
Hibernate の動きがそのまま JPA 仕様になったらしい.
Linda にそれはやめてと言ったんだけどなぁ.その時「それはよくないね」みたいに言ってたんだけどなぁ.
でもでも,「Pro EJB 3 Java Persistence API (isbn:1590596455)」を見ると,どうやら TopLink でも distinct しなきゃいけないみたいだし.
しくしくしく.


WHERE 句もまぁ,似たようなものです.

        List<Employee> list = select().from(Employee.class)
                .where(eq("employee.bloodType", literal("AB")))
                .getResultList(em);

ここでは血液型が AB の従業員を選択しています.
ちょっと悩ましいのは,String 引数の扱い.
上の場合,eq() メソッドは 2 つの引数を受け取るのですが,現在そこに String を渡すと employee.bloodType のような Path Expression として解釈しています.
そのため,'AB' のようなリテラルを指定するには literal() を介する必要があります.
油断すると忘れそうなのがイマイチなのですが,Stringリテラルだと解釈すると Path Expression の時に明示的に path() とか介さないといけなくなるのでどっちもどっちなんですよね.
いっそ Stringコンパイルエラーになるようにしようかとも思ったのですが,それはそれで面倒になるし.
この辺はまだ悩みながらやっているので,いいアイディアあったらお願いします.


where() も可変長引数を受け取ります.

        List<Employee> list = select().from(Employee.class, "e")
                .where(eq("e.height", 150), eq("e.weight", 45))
                .getResultList(em);

このように並べると AND で繋がれます.
OR を使う場合は

        List<Employee> list = select().from(Employee.class, "e")
                .where(or(lt("e.weight", 45), gt("e.weight", 70)))
                .getResultList(em);

とします.and() もあります.いずれも可変長引数を受け取ります.
基本的な演算子やファンクションなどはだいたい揃ってます.


集計関数や GROUP BY,HAVING,ORDER BY もサポート.

        List<Object[]> list = select("p.id", count("c"))
                .from(join(Customer.class, "c").inner("c.prefectural", "p"))
                .groupby("p.id")
                .having(ge(count("c"), 3))
                .orderby("p.id")
                .getResultList(em);

顧客が 3 件以上ある県とその顧客数を県の ID 順に取得します.
ここで orderby()count() 順にすることができない気のせいが.
JPQL の BNF 的にできるとは思えない...
できそうにないといえば,

        List<Integer> list = select(add("e.height", 10)).from(Employee.class, "e").getResultList();

JPQL でいうと

SELECT e.height + 10 FROM Employee AS e

みたいに SELECT 句に集約関数以外の計算式を書くことも BNF 的にできない気のせいが...
Hibernate ではできちゃうけど (苦笑).
「Pro EJB 3 Java Persistence API (isbn:1590596455)」の例には出てこないので,TopLink に配慮してできなくなってる?
ともあれ (JW),JPQL はまだまだ拡張されて完全な SQL に近づいていくんだろうなぁ (苦笑).


といった感じで,Criteria そのものはいい感じになったかなぁ,と.
それもこれも static import,可変長引数,Generics などなど,Tiger 様のおかげです.
でもでも,おかげで CriteriaOperations は超巨大になってしまった...
今現在でも 1500 行くらいあるよ?
心より恥じる.


まだ対応ができていないのは,大きなところでは副問い合わせ.
それに関連して EXISTS とかその辺.
おいおい実装していきます.