Hibernate 入門記 問い合せ その3 式いろいろ

『たすけてくださぁぁぁぁぁい!!!!!』
書いてみたかっただけです.m(__)m
謎めき系ではないこの日記としては,これもちゃんと解説しておかねば! ということに気づきました.
これは単なるセカチューではありません.「『「何でもセカチュー風に叫んでみる」遊び』をしている眞鍋かをり風に書いてみる」遊びをしているのです.
元ネタはご存じ,「眞鍋かをりのここだけの話」です.
08/25 の冒頭も元ネタは「眞鍋かをりのここだけの話」です.突然「ちがうの。」とかお姉さん言葉になったのはそのためです.そこんとこよろしくです.


そんなわけで (どんなわけで?),何が助けて欲しいかって,前々回に作成したテーブルや永続クラスではお試しできないものがあったわけです.
そこで,今回はサンプルコードを変更します.ほらぁ,助けて欲しくなるでしょう?
そんなわけで (どんなわけで?),新しいテーブル定義.

CREATE TABLE MAGAZINE (
    ID INTEGER IDENTITY PRIMARY KEY,
    NAME VARCHAR,
    PUBLISHER VARCHAR
)

CREATE TABLE MODEL (
    ID INTEGER IDENTITY PRIMARY KEY,
    FIRST_NAME VARCHAR,
    LAST_NAME VARCHAR,
    AGE INTEGER,
    MAGAZINE INTEGER,
    MAGAZINE_INDEX INTEGER,
    FOREIGN KEY (MAGAZINE) REFERENCES MAGAZINE (ID)
)

雑誌のテーブルは何も変わっていません.
モデルのテーブルは,名前を姓と名に分けて,外部キー制約を指定しました.さらに,雑誌への関連をリスト (順序付き) にするために,MAGAZINE_INDEX カラムを追加しています.
そんなわけで (どんなわけで?),新しい雑誌の永続クラス.

package study;
import java.util.ArrayList;
import java.util.List;

public class Magazine {
    int id = -1;
    String name;
    String publisher;
    List model;

    public Magazine() {
    }

    public Magazine(String name, String publisher) {
        this.name = name;
        this.publisher = publisher;
        this.model = new ArrayList();
    }

    public String toString() {
        return name + "(" + publisher + "):" + model;
    }
}

モデルへの関連を Set から List に変更しています.
そして雑誌のマッピングファイル.

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
    "-//Hibernate/Hibernate Mapping DTD 2.0//EN"
    "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd"
>
<hibernate-mapping auto-import="false" package="study" default-access="field">
    <class name="Magazine">
        <id name="id" unsaved-value="-1">
            <generator class="identity"/>
        </id>
        <property name="name"/>
        <property name="publisher"/>
        <list name="model" cascade="save-update">
        	<key column="magazine"/>
        	<index column="magazine_index"/>
        	<one-to-many class="Model"/>
        </list>
    </class>
</hibernate-mapping>

続いてモデルの永続クラス.

package study;

public class Model {
    int id = -1;
    Name name;
    int age;
    Magazine magazine;

    public Model() {
    }
    public Model(String firstName, String lastName, int age, Magazine magazine) {
        this.name = new Name(firstName, lastName);
        this.age = age;
        this.magazine = magazine;
    }
    public String toString() {
        return name + "(" + age + ")";
    }
}

雑誌への関連を持つようになりました.
名前は姓と名でカラムが分かれたため,Name というクラスを使っています.
その名前クラス.

package study;

public class Name {
    String firstName;
    String lastName;

    public Name() {
    }
    public Name(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    public String toString() {
        return firstName + " " + lastName;
    }
}

そしてモデルのマッピングファイル.

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
    "-//Hibernate/Hibernate Mapping DTD 2.0//EN"
    "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd"
>
<hibernate-mapping auto-import="false" package="study" default-access="field">
    <class name="Model">
        <id name="id" unsaved-value="-1">
            <generator class="identity"/>
        </id>
        <component name="name" class="study.Name">
            <property name="firstName" column="FIRST_NAME"/>
            <property name="lastName" column="LAST_NAME"/>
        </component>
        <property name="age"/>
        <many-to-one name="magazine" class="study.Magazine"/>
    </class>
</hibernate-mapping>

姓と名の二つのカラムを name というプロパティにマッピングしています.
これでコンポーネントが必要な例も,1対多関連が必要な例も試せます.
そしてデータ作成用のクラス.

package study;
import net.sf.hibernate.Session;

public class Save {
    public static void main(String[] args) {
        try {
            HibernateTemplate template = new HibernateTemplate();

            template.process(new HibernateCallback() {
                public Object doProcess(Session session) throws Exception {
                    Magazine cancam = new Magazine("CanCam", "Shogakukan");
                    session.save(cancam);
                    Magazine oggi = new Magazine("Oggi", "Shogakukan");
                    session.save(oggi);
                    Magazine jj = new Magazine("JJ", "Kobunsha");
                    session.save(jj);
                    Magazine vivi = new Magazine("ViVi", "Kodansha");
                    session.save(vivi);
                    Magazine ray = new Magazine("Ray", "Shufunotomo");
                    session.save(ray);

                    cancam.model.add(new Model("Yuri", "Ebihara", 24, cancam));
                    cancam.model.add(new Model("Asami", "Usuda", 19, cancam));
                    cancam.model.add(new Model("Yu", "Yamada", 20, cancam));
                    oggi.model.add(new Model("Satoko", "Koizumi", 23, oggi));
                    vivi.model.add(new Model("Jun", "Hasegawa", 18, vivi));
                    ray.model.add(new Model("Karina", null, 20, ray));
                    session.save(new Model("Sayo", "Aizawa", 26, null));

                    return null;
                }
            });
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }
}

前々回は Insert という名前でしたが,今回は少し Hibernate っぽく Save にしてみました.どうでもいい? 残念!!!!
それから,今回は 元 CanCam モデルで現在は Oggi で活躍中の里子ちゃん初登場です.TV CM にも出てますね.どうでもいい? 残念!!!!
こいつを実行しておきます.
問い合せ用のクラスは前々回と同じです.


ということで,前回試せなかった例を一つやってみましょう.
FROM 句で結合を指定しなくても,WHERE 句で関連を辿ると勝手に結合されるというやつ.

from 
    study.Model model 
where 
    model.magazine.publisher = 'Shogakukan'

その実行結果.

Yuri Ebihara(24)
Asami Usuda(19)
Yu Yamada(20)
Satoko Koizumi(23)

CanCam モデル 3 人に加えて,同じく小学館発行の Oggi から里子ちゃんも引っかかりました.
このとき,実際に実行された SQL は...

select 
     model0_.id as id, model0_.FIRST_NAME as FIRST_NAME, 
     model0_.LAST_NAME as LAST_NAME, model0_.age as age, 
     model0_.magazine as magazine 
from 
     Model model0_, Magazine magazine1_ 
where 
     (magazine1_.publisher='Shogakukan'  and model0_.magazine=magazine1_.id)

と,きっちり結合されています.


では,そろそろ今日の学習を始めましょう.前振り長すぎ? 残念!!!!
そんなわけで (どんなわけで?),「11.8. Expressions」です.
HQL で扱うことの出来る式がずらずら〜っと.これは辛いなぁ... とりあえず一覧.

  • +,-,*,/
  • =,>=,<=,><,!=,like
  • and,or,not
  • ||
  • upper(),lower()
  • ( )
  • in,between,is null
  • ?,:name
  • リテラル

なんか,どれもどってことない感じ.そんなわけで (どんなわけで?),スキップ,スキップ♪


次に出てくるのは 真偽値の扱い.HQL 中で truefalse を使った場合に,それを別の値に置き換えることが出来るようです.
そのためには,hibernate.cfg.xml にプロパティを次のように記述します.

<property name="hibernate.query.substitutions">true 1, false 0</property>

すると,true は 1 に,false は 0 に置き換えられます.
ということで次の問い合せ

from study.Model model where model.id = true

idboolean ではなくて int なので意味はないのですが,ともかくこれを実行!!!!

Yuri Ebihara(24)

id が 1 なのはエビちゃんなんですね.
使っている RDBMS が BOOLEAN なデータ型をサポートしていない場合に重宝しそうです.


次はコレクションに関連する話題.
コレクションのサイズを WHERE 句に含めることが出来るそうです.

from study.Magazine magazine where size(magazine.model) > 1

こいつを実行!!

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

専属モデルを二人以上登録してあるのは CanCam だけです.ばっちり.
この時に実行された SQL は次の通り.

select 
    magazine0_.id as id, magazine0_.name as name, magazine0_.publisher as publisher 
from 
    Magazine magazine0_ 
where 
    ((select count(*) from Model model1_ where magazine0_.id=model1_.magazine) > 1)

勝手に副問い合せをしてくれるのですね.


続いては,順序付きコレクション (リストや配列) の先頭の要素を参照する minElement と最後の要素を参照する maxElement です.これのために雑誌とモデルの関連をリストにしたのです♪
そんなわけで (どんなわけで?),次の問い合せ.

select 
    model 
from 
    study.Magazine magazine 
        inner join magazine.model model 
where 
    model = magazine.model.minElement

各雑誌に関連づけられた,最初のモデルだけを取ってこようという問い合せ.随分回りくどいやり方という気もしますが,ここは minElement を使うことに意義があるのです.
その実行結果.

Yuri Ebihara(24)
Satoko Koizumi(23)
Jun Hasegawa(18)
Karina null(20)

おおぉぉぉっ,うまくできたっぽい.ちなみに CanCam 以外はモデルは一人しかいないのであまりおもしろくありません.
その時の SQL

select 
    model1_.id as id, model1_.FIRST_NAME as FIRST_NAME, model1_.LAST_NAME as LAST_NAME, 
    model1_.age as age, model1_.magazine as magazine 
from 
    Magazine magazine0_ 
        inner join Model model1_ on magazine0_.id=model1_.magazine 
where 
    (model1_.id = (select min(id) from Model model2_ where magazine0_.id=model2_.magazine))

なるほど.


minElement および maxElement は,プロパティ風の使い方だけでなく,関数風に使うことも出来るそうです.
そんなわけで (どんなわけで?),関数風な呼び出しで最後のモデルだけを取得.

select 
    model 
from 
    study.Magazine magazine 
        inner join magazine.model model 
where 
    model = maxElemtn(magazine.model)

その問い合せ結果.

Yu Yamada(20)
Satoko Koizumi(23)
Jun Hasegawa(18)
Karina null(20)

ふむ.


コレクションの最初の要素または最後の要素のインデックスを取得する,minIndex および maxIndex というのも用意されているようです.

from 
    study.Magazine magazine 
where 
    magazine.model.maxIndex > 0

最後の要素のインデックスが 0 を超えている,つまり二人以上のモデルを抱えている雑誌のみを問い合せます.
その実行結果.

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

ふむふむ.
その時の SQL

select 
    magazine0_.id as id, magazine0_.name as name, magazine0_.publisher as publisher 
from 
    Magazine magazine0_ 
where 
    ((select max(magazine_index) from Model model1_ where magazine0_.id=model1_.magazine) > 0)

ふむふむふむ.


次は any, some, all, exists, in などなど.ふーっ,そろそろお疲れ気味.
こいつらを使うには,コレクションに対して elementsindices という関数を適用するようです.
まずは簡単なものから.

from 
    study.Magazine magazine 
where 
    exists elements(magazine.model)

専属モデルが存在する雑誌を問い合せています.
その実行結果.

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

専属モデルを登録していない JJ が表示されていません.ばっちり.
その時の SQL

select 
    magazine0_.id as id, magazine0_.name as name, magazine0_.publisher as publisher 
from 
    Magazine magazine0_ 
where 
    (exists(select model1_.id from Model model1_ where magazine0_.id = model1_.magazine))


もう一つ.

from 
    study.Magazine magazine 
where 
    2 in indices(magazine.model)

indices(magazine.model) は,雑誌が持っている順序付きコレクションのインデックスの集合です.
その実行結果.

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

インデックスに 2 を含んでいるのは CanCam だけです.その他の雑誌は高々モデル一人しかいないので,インデックスは (あっても) 0 しかありません.
その時の SQL

select 
    magazine0_.id as id, magazine0_.name as name, magazine0_.publisher as publisher 
from 
    Magazine magazine0_ 
where 
    (2 in (select model1_.magazine_index from Model model1_ where magazine0_.id = model1_.magazine))

この例は,マップを使った方がおもしろかったですね.残念!!!!


これら size, elements, indices, minIndex, maxIndex, minElement, maxElement には,次の制限があるとのこと.

  • WHERE 句で使えるのは,RDBMS が副問い合せをサポートしている場合のみ.
  • SELECT 句で使えるのは,elementsindices のみ.

ふーん,SELECT 句でも使えるんだぁ.


ということでやってみました.

select 
    elements(magazine.model) 
from 
    study.Magazine magazine

その実行結果.

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

ほほぉ.
その時の SQL

select 
    model2_.id as x0_0_ 
from 
    Magazine magazine0_, Model model1_, Model model2_ 
where 
    magazine0_.id = model1_.magazine and model1_.id = model2_.id

むむぅ?
なぜにモデルを2回も結合しているのでしょうか?
model1_ はなんの役にも立っていないような?
ちょっと気になりますね...


ともあれ (JW),次へ行きましょう.
インデックス付きのコレクションすなわち,リスト,配列,マップの場合,WHERE 句ではそのインデックスを指定できるそうです.

from 
    study.Magazine magazine 
where 
    magazine.model[0].age > 20

その実行結果.

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

その時の SQL

select 
    magazine0_.id as id, magazine0_.name as name, magazine0_.publisher as publisher 
from 
    Magazine magazine0_, Model model1_ 
where 
    (model1_.age > 20 and magazine0_.id = model1_.magazine and model1_.magazine_index = 0)

ふむ.


[] の内側には,式も記述できるそうです.

from 
    study.Magazine magazine 
where 
    magazine.model[maxIndex(magazine.model)].age <= 20

その実行結果.

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

その時のSQL

select 
    magazine0_.id as id, magazine0_.name as name, magazine0_.publisher as publisher 
from 
    Magazine magazine0_, Model model1_ 
where 
    (model1_.age <= 20 and magazine0_.id = model1_.magazine and model1_.magazine_index = 
        (select max(magazine_index) from Model model2_ where magazine0_.id=model2_.magazine))

ふむふむ.


さらに,1対多関連の場合,index() という関数を使うことで,インデックスを得ることが出来るそうです.

select 
    index(model), model 
from 
    study.Magazine magazine 
        inner join magazine.model model

その実行結果.

[0, Yuri Ebihara(24)]
[1, Asami Usuda(19)]
[2, Yu Yamada(20)]
[0, Satoko Koizumi(23)]
[0, Jun Hasegawa(18)]
[0, Karina null(20)]

その時のSQL

select 
    model1_.id as id, model1_.FIRST_NAME as FIRST_NAME, model1_.LAST_NAME as LAST_NAME, 
    model1_.age as age, model1_.magazine as magazine, model1_.magazine_index as x0_0_, 
    model1_.id as x1_0_ 
from 
    Magazine magazine0_ 
        inner join Model model1_ on magazine0_.id = model1_.magazine

ふむふむふむ.


最後は単調に試しただけになってしまいましたが,一応「11.8. Expressions」終了です.
ちょっと雑すぎ? でも,文字数だけは多いんです.なので,


『はんぶんにしてくださぁぁぁぁぁい!!!!!』©眞鍋かをり


と思った人がきっといるはずだ.