Hibernate 入門記 クリテリア

今日は「Chapter 12. Criteria Queries」です.
以前どこかでちょっと使ったことがありますね.いつだっけ? ごそごそ... 「セッションその5 問い合せその他」ですね.あれ?
ぐはぁっ,昨日やったフィルタもこの日に学習済みじゃないですか!? 完璧に忘れてましたよ.しかも,メモリ上で処理してくれるのかな? とか同じ疑問持っているし.
...はい、学習できない子なんです。©眞鍋かをり
学習できないっていうか,同じ学習二回やってしまったというか,一粒で二度おいしいってやつですか?(違).
ともあれ (JW),今日学習するのは文字列ではなくオブジェクトで問い合せ条件を構築することが出来るってやつですね.
この criteria とか criterion とかって,日本語にすると何になるのでしょう? 辞書を引くと「基準」とか出てきますが,ここでの criteria には今ひとつ合わない感じ.まいっか.困ったときはカタカナです.昔からそう決まっています.


そんなわけで (どんなわけで?),クリテリアについての学習です.まずは「12.1. Creating a Criteria instance」.
クリテリアは,

  • Criteria

という interface で表現されます.こいつを入手するには,Session

    • Criteria createCriteria(Class persistentClass)

を使います.
Criterion を入手したら,

    • Criteria setMaxResults(int maxResults)

で取得する結果の上限を設定することが出来ます.そして,

    • List list()

で問い合せ結果を取得することが出来ます.
と,ここまでは「セッションその5 問い合せその他」で学習済みなのですが,一粒で二度おいしい日記としては,もう一度味わってみるべきでしょう.
そんなわけで (どんなわけで?),前々回以来使っているサンプルの問い合せ部分 (匿名内部クラスの中) を次のように修正.

                            return session.createCriteria(Model.class)
                                    .setMaxResults(3)
                                    .list();

その実行結果.

Sayo Aizawa(26)
Yuri Ebihara(24)
Asami Usuda(19)

いい結果だ♪
ちょっと解せないのは,その時の SQL

select top ? 
    this.id as id1_, this.FIRST_NAME as FIRST_NAME1_, this.LAST_NAME as LAST_NAME1_, 
    this.age as age1_, this.magazine as magazine1_, 
    magazine1_.id as id0_, magazine1_.name as name0_, magazine1_.publisher as publisher0_ 
from 
    Model this 
        left outer join Magazine magazine1_ on this.magazine = magazine1_.id 
where 
    1=1

なぜに雑誌を外部結合しているのだろう? かなり意味ふめ.そしてすっごく気になります.Hibernate も謎めき系?


次は「12.2. Narrowing the result set」.
ナローイングとか見ると,つい CORBA かと思ってしまったりするのですが,それとは全然関係ありませんでした.残念!!!!
…イヤべつに残念でもなんでもないのですが、©眞鍋かをり
この場合のナローイングは,問い合せ結果をさらに絞り込むという意味ですね.
そのために使うのが,

  • Criterion

という interface.それを取得するために使うのが,

  • Expression

という class
この Expression には,イヤになるくらい多くのメソッドがあります.行数稼ぎにはちょうどいい (違) のでリストアップ!!

    • SimpleExpression eq(String propertyName, Object value)
    • SimpleExpression like(String propertyName, Object value)
    • SimpleExpression like(String propertyName, String value, MatchMode matchMode)
    • Criterion ilike(String propertyName, String value, MatchMode matchMode)
    • Criterion ilike(String propertyName, Object value)
    • SimpleExpression gt(String propertyName, Object value)
    • SimpleExpression lt(String propertyName, Object value)
    • SimpleExpression ge(String propertyName, Object value)
    • SimpleExpression le(String propertyName, Object value)
    • Criterion between(String propertyName, Object lo, Object hi)
    • Criterion in(String propertyName, Object[] values)
    • Criterion in(String propertyName, Collection values)
    • Criterion isNull(String propertyName)
    • Criterion isNotNull(String propertyName)
    • Criterion eqProperty(String propertyName, String otherPropertyName)
    • Criterion ltProperty(String propertyName, String otherPropertyName)
    • Criterion leProperty(String propertyName, String otherPropertyName)
    • Criterion and(Criterion lhs, Criterion rhs)
    • Criterion or(Criterion lhs, Criterion rhs)
    • Criterion not(Criterion expression)
    • Criterion sql(String sql, Object[] values, Type[] types)
    • Criterion sql(String sql, Object value, Type type)
    • Criterion sql(String sql)
    • Conjunction conjunction()
    • Disjunction disjunction()
    • Criterion allEq(Map propertyNameValues)

はじめの方のやつは,おおむね名前を見るだけで意味が分かりますね.
軽く試しながら行きましょう.
まずは適当に like でも.

                            return session.createCriteria(Model.class)
                                    .add(Expression.like("name.firstName", "Yu%"))
                                    .list();

その結果.

Yuri Ebihara(24)
Yu Yamada(20)


次は gt (grater than,FORTRAN 思い出すなー) でも.

                            return session.createCriteria(Model.class)
                                    .add(Expression.gt("age", new Integer(20)))
                                    .list();

その結果.

Sayo Aizawa(26)
Yuri Ebihara(24)
Satoko Koizumi(23)


xxProperty() は,リテラルとではなくて,他のプロパティの値と比較する場合に使います.

                            return session.createCriteria(Model.class)
                                    .add(Expression.ltProperty(
                                            "name.firstName", "name.lastName"))
                                    .list();

その結果.

Asami Usuda(19)


sql() を使うと,SQL の断片を指定できるようです.SQL の断片中では,{alias} という形でテーブルの別名を取得できるそうです.

                            return session.createCriteria(Model.class)
                                    .add(Expression
                                            .sql("{alias}.age > (select avg(age) from model)"))
                                    .list();

その結果.

Sayo Aizawa(26)
Yuri Ebihara(24)
Satoko Koizumi(23)


conjunction()disjunction() は,複数の式を and / or でつなぐ場合に使うようです.

                            return session.createCriteria(Model.class)
                                    .add(Expression.disjunction()
                                            .add(Expression.like("name.firstName", "Yu%"))
                                            .add(Expression.eq("age", new Integer(18)))
                                            .add(Expression.isNull("magazine")))
                                    .list();

その結果.

Sayo Aizawa(26)
Yuri Ebihara(24)
Yu Yamada(20)
Jun Hasegawa(18)

こんなものかなぁ.


そんなわけで (どんなわけで?),次は「12.3. Ordering the results」です.
ORDER BY 句も指定できるそうです.
それには,Criteria

    • Criteria addOrder(Order order)

を使います.Orderインスタンスは,Orderstatic メソッド,

    • Order asc(String propertyName)
    • Order desc(String propertyName)

を使って取得します.

                            return session.createCriteria(Model.class)
                                    .addOrder(Order.asc("magazine"))
                                    .addOrder(Order.desc("age"))
                                    .addOrder(Order.asc("name.firstName"))
                                    .addOrder(Order.asc("name.lastName"))
                                    .list();

その結果.

Sayo Aizawa(26)
Yuri Ebihara(24)
Yu Yamada(20)
Asami Usuda(19)
Satoko Koizumi(23)
Jun Hasegawa(18)
Karina null(20)


続いて「12.4. Associations」.
関連づけられたオブジェクトのプロパティを使って条件を絞るには,関連を指定して Criteria を作成するようです.

                            return session.createCriteria(Magazine.class)
                                    .createCriteria("model")
                                            .add(Expression.gt("age", new Integer(20)))
                                    .list();

今度は雑誌を問い合せています.その条件として,関連づけられたモデルが二十歳より上と指定しています.
その結果.

CanCam(Shogakukan):[Yuri Ebihara(24), Asami Usuda(19), Yu Yamada(20)]
Oggi(Shogakukan):[Satoko Koizumi(23)]

ふむ.実質的には,二十歳より上のモデルと関連を持っている雑誌を問い合せているということですね.


同じ結果を得るのに,別の方法もあるようです.

                            return session.createCriteria(Magazine.class)
                                    .createAlias("model", "model")
                                    .add(Expression.gt("model.age",new Integer(20)))
                                    .list();

ここではまずエイリアスを作成し,そのエイリアスを使って条件を指定しています.
その結果.

CanCam(Shogakukan):[Yuri Ebihara(24), Asami Usuda(19), Yu Yamada(20)]
Oggi(Shogakukan):[Satoko Koizumi(23)]


直前の二つの問い合せですが,問い合せ結果の CanCam を見ると,二十歳以下のモデルも表示されています.
問い合せ条件では,二十歳より上のモデルを指定しているにもかかわらず.
これは,あくまでも雑誌を検索する条件として,関連づけられているモデルを参照したというだけで,結果として得られた雑誌の関連には影響を及ぼさないということです.
しかし,それでは不十分で,条件を満たしたモデルが必要な場合もあるでしょう.そんな場合には,Criteria

    • Criteria setResultTransformer(ResultTransformer resultTransformer)

を使うことが出来るそうです.ドキュメントでは returnMaps() となっていますが,それはすでに @deprecated になっています.残念!!!!
こいつの引数に Criteria.ALIAS_TO_ENTITY_MAP を指定すると,問い合せ結果は createCriteria(Class) で指定した永続オブジェクトではなく,永続オブジェクトと条件を満たした関連オブジェクトのマップになるようです.
こんな感じ.

                            return session.createCriteria(Magazine.class)
                                    .createAlias("model", "model")
                                    .add(Expression.gt("model.age", new Integer(20)))
                                    .setResultTransformer(Criteria.ALIAS_TO_ENTITY_MAP)
                                    .list();

その結果.

{model=Yuri Ebihara(24), this=CanCam(Shogakukan):[Yuri Ebihara(24), Asami Usuda(19), Yu Yamada(20)]}
{model=Satoko Koizumi(23), this=Oggi(Shogakukan):[Satoko Koizumi(23)]}

結果は雑誌ではなく,マップのリストになりました.
そのマップは二つのエントリを含んでいて,一つは雑誌,もう一つは条件を満たしたモデルです.
一つの雑誌に,条件を満たししたモデルが複数ある場合は,それぞれの組み合わせごとにマップが返されます.
モデルの条件を二十歳以下に変えて確認.

                            return session.createCriteria(Magazine.class)
                                    .createAlias("model", "model")
                                    .add(Expression.le("model.age", new Integer(20)))
                                    .setResultTransformer(Criteria.ALIAS_TO_ENTITY_MAP)
                                    .list();

その結果.

{model=Asami Usuda(19), this=CanCam(Shogakukan):[Yuri Ebihara(24), Asami Usuda(19), Yu Yamada(20)]}
{model=Yu Yamada(20), this=CanCam(Shogakukan):[Yuri Ebihara(24), Asami Usuda(19), Yu Yamada(20)]}
{model=Jun Hasegawa(18), this=ViVi(Kodansha):[Jun Hasegawa(18)]}
{model=Karina null(20), this=Ray(Shufunotomo):[Karina null(20)]}

ふーん.扱いやすい... のか?
ま,こういうのもあると覚えておこう.え? どうせ忘れるだろうって? 失礼な! 三歩歩くまでは大丈夫! なはず!!
実は「いすから立ち上がると忘れてる」ってよく言われるのは内緒だ.
次行きますよ! 次,次!(エビちゃん風)


そんなわけで (どんなわけで?),「12.5. Dynamic association fetching」です.
関連づけられたオブジェクトをフェッチするモードを指定できるようです.それには,Criteria

    • Criteria setFetchMode(String associationPath, FetchMode mode)

を使います.指定できるモードは,

LAZY
遅延ロードします.
EAGER
遅延ロードしません (外部結合します).
DEFAULT
マッピングファイルの記述に従います.

試してみましょう.まずは LAZY.

                           return session.createCriteria(Magazine.class)
                                    .setFetchMode("model", FetchMode.LAZY)
                                    .list();

その結果.

CanCam(Shogakukan):[Yuri Ebihara(24), Asami Usuda(19), Yu Yamada(20)]
Oggi(Shogakukan):[Satoko Koizumi(23)]
JJ(Kobunsha):[]
ViVi(Kodansha):[Jun Hasegawa(18)]
Ray(Shufunotomo):[Karina null(20)]

その時のSQL (全て).

Hibernate: select 
               this.id as id0_, this.name as name0_, this.publisher as publisher0_ 
           from 
               Magazine this 
           where 
               1=1
Hibernate: select 
               model0_.magazine as magazine__, model0_.id as id__, 
               model0_.magazine_index as magazine6___, model0_.id as id0_, 
               model0_.FIRST_NAME as FIRST_NAME0_, model0_.LAST_NAME as LAST_NAME0_, 
               model0_.age as age0_, model0_.magazine as magazine0_ 
           from 
               Model model0_ 
           where 
               model0_.magazine=?
Hibernate: select 
               model0_.magazine as magazine__, model0_.id as id__, 
               model0_.magazine_index as magazine6___, model0_.id as id0_, 
               model0_.FIRST_NAME as FIRST_NAME0_, model0_.LAST_NAME as LAST_NAME0_, 
               model0_.age as age0_, model0_.magazine as magazine0_ 
           from 
               Model model0_ 
           where 
               model0_.magazine=?
Hibernate: select 
               model0_.magazine as magazine__, model0_.id as id__, 
               model0_.magazine_index as magazine6___, model0_.id as id0_, 
               model0_.FIRST_NAME as FIRST_NAME0_, model0_.LAST_NAME as LAST_NAME0_, 
               model0_.age as age0_, model0_.magazine as magazine0_ 
           from 
               Model model0_ 
           where 
               model0_.magazine=?
Hibernate: select 
               model0_.magazine as magazine__, model0_.id as id__, 
               model0_.magazine_index as magazine6___, model0_.id as id0_, 
               model0_.FIRST_NAME as FIRST_NAME0_, model0_.LAST_NAME as LAST_NAME0_, 
               model0_.age as age0_, model0_.magazine as magazine0_ 
           from 
               Model model0_ 
           where 
               model0_.magazine=?
Hibernate: select 
               model0_.magazine as magazine__, model0_.id as id__, 
               model0_.magazine_index as magazine6___, model0_.id as id0_, 
               model0_.FIRST_NAME as FIRST_NAME0_, model0_.LAST_NAME as LAST_NAME0_, 
               model0_.age as age0_, model0_.magazine as magazine0_ 
           from 
               Model model0_ 
           where 
               model0_.magazine=?

遅延ロードなので,細切れに SQL が発行されています.
次に EAGER.

                           return session.createCriteria(Magazine.class)
                                    .setFetchMode("model", FetchMode.EAGER)
                                    .list();

その結果.

CanCam(Shogakukan):[Yuri Ebihara(24), Asami Usuda(19), Yu Yamada(20)]
CanCam(Shogakukan):[Yuri Ebihara(24), Asami Usuda(19), Yu Yamada(20)]
CanCam(Shogakukan):[Yuri Ebihara(24), Asami Usuda(19), Yu Yamada(20)]
Oggi(Shogakukan):[Satoko Koizumi(23)]
JJ(Kobunsha):[]
ViVi(Kodansha):[Jun Hasegawa(18)]
Ray(Shufunotomo):[Karina null(20)]

その時のSQL

Hibernate: select 
               this.id as id1_, this.name as name1_, this.publisher as publisher1_, 
               model1_.magazine as magazine__, model1_.id as id__, 
               model1_.magazine_index as magazine6___, model1_.id as id0_, 
               model1_.FIRST_NAME as FIRST_NAME0_, model1_.LAST_NAME as LAST_NAME0_, 
               model1_.age as age0_, model1_.magazine as magazine0_ 
           from 
               Magazine this 
                   left outer join Model model1_ on this.id=model1_.magazine 
           where 
               1=1

なるほど,たしかに一気にフェッチしてくれているようです.しかし...
Session#find(String) でもそうですが,外部結合使って一気にフェッチすると,親オブジェクト (この場合は雑誌) は同じインスタンスを複数回受け取ってしまいますね.
でも大丈夫です.たぶん.
さきほどの Criteria#setResultTransformer(ResultTransformer) ですが,ここで指定できる ResultTransformer として,Criteria.DISTINCT_ROOT_ENTITY というものが用意されています.いかにもな名前じゃないですか.さっそく試してみましょう.

                            return session.createCriteria(Magazine.class)
                                    .setFetchMode("model", FetchMode.EAGER)
                                    .setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY)
                                    .list();

その結果.

CanCam(Shogakukan):[Yuri Ebihara(24), Asami Usuda(19), Yu Yamada(20)]
Oggi(Shogakukan):[Satoko Koizumi(23)]
JJ(Kobunsha):[]
ViVi(Kodansha):[Jun Hasegawa(18)]
Ray(Shufunotomo):[Karina null(20)]

その時のSQL

Hibernate: select 
               this.id as id1_, this.name as name1_, this.publisher as publisher1_, 
               model1_.magazine as magazine__, model1_.id as id__, 
               model1_.magazine_index as magazine6___, model1_.id as id0_, 
               model1_.FIRST_NAME as FIRST_NAME0_, model1_.LAST_NAME as LAST_NAME0_, 
               model1_.age as age0_, model1_.magazine as magazine0_ 
           from 
               Magazine this 
                   left outer join Model model1_ on this.id=model1_.magazine 
           where 
               1=1

やったね!
これですよ,これが欲しかったんですよ.っていうか,Session#find(String) でも何かあるかも? 要調査です.


さぁ,最後は「12.6. Example queries」です.
なんか,

  • Example

というクラスがあるようです.でも,単なるサンプルではありません.
こいつの

    • Example create(Object entity)

を使うと,引数で渡したエンティティと同値のエンティティを探してくれるような問い合わせ条件を作成してくれるようです.

                    Magazine cancam = new Magazine("CanCam", "Shogakukan");
                    return session.createCriteria(Magazine.class)
                            .add(Example.create(cancam))
                            .list();

その結果.

CanCam(Shogakukan):[Yuri Ebihara(24), Asami Usuda(19), Yu Yamada(20)]

ふむ.


さらにこの Example,作成される問い合わせ条件を調整するための様々なメソッドを持っているようです.しかし...
もう疲れたのでちょっとだけ試して終わりにします.

                    Model model = new Model();
                    model.name = new Name();
                    model.name.firstName = "Yu%";
                    return session.createCriteria(Model.class)
                            .add(Example.create(model)
                                    .enableLike()
                                    .excludeProperty("age"))
                            .list();

モデルの名前を LIKE で検索します.age は検索条件から除外しています.参照型のプロパティの場合,null だと検索条件から勝手に外されるのですが,この場合の age はプリミティブ型であるため,通常は検索条件に含められてしまいます.それを Example#excludeProperty(String) で明示的に除外するようにしました.
その結果.

Yuri Ebihara(24)
Yu Yamada(20)

\(^o^)/


やれやれ,これで 12 章も完了.なんかいいペースだ.っていうか雑かなぁ?
『許してくださぁぁぁぁぁい!!!!!』©眞鍋かをり