Hibernate 入門記 問い合せ その1 from,join,select

ごめんなさい( -_-)
入門記してませんでした…。
ちがうの、月曜日は CanCam の発売日だったの。
まぁ言い訳ですが。
... 夏風邪はともかく,CanCam は言い訳になるのだろうか?


ともあれ,今日から「Chapter 11. HQL: The Hibernate Query Language」,Hibernate の問い合せ言語である,HQL についての学習です.とはいえ,これまでも困ったときにちょこちょこ見に来ていたのですけどね.まぁいいでしょう.頑張って学習しましょう.


その前に.
いつもだと,一通り学習してからお試しコーナーへと移るわけですが,今回の場合,HQL を学習するたびにちまちま試す方がよさげ.ということで,いきなりテーブル定義から始めます!!

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

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

すぐに結合とか出てくるのが分かっているので (^^; テーブル二つ用意してます.
で,雑誌の永続クラス.

package study;
import java.util.HashSet;
import java.util.Set;

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

    public Magazine() {
    }
    public Magazine(String name, String publisher) {
        this.name = name;
        this.publisher = publisher;
        this.model = new HashSet();
    }
    public String toString() {
        return name + "(" + publisher + "):" + model;
    }
}

雑誌のマッピングファイル.

<?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"/>
        <set name="model" cascade="save-update">
            <key column="magazine"/>
            <one-to-many class="Model"/>
        </set>
    </class>
</hibernate-mapping>

次にモデルの永続クラス.

package study;

public class Model {
    int id = -1;
    String name;
    int age;

    public Model() {
    }
    public Model(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public String toString() {
        return name + "(" + age + ")";
    }
}

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

<?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>
        <property name="name"/>
        <property name="age"/>
    </class>
</hibernate-mapping>

二つのマッピングファイルを hibernate.cfg.xml に記述します.
そしてデータ作成用のクラス.

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

public class Insert {
    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 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.model.add(new Model("Asami Usuda", 19));
                    cancam.model.add(new Model("Yu Yamada", 20));
                    vivi.model.add(new Model("Jun Hasegawa", 18));
                    ray.model.add(new Model("Karina", 20));
                    session.save(new Model("Sayo Aizawa", 26));

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

JJ にはモデルがいません.ViVi や CLASSY に出てる (出てた?) 紗世ちゃんは専属じゃないのでどの雑誌にも属していません (新創刊の雑誌「BOAO」はこれからなのだ).
こいつを実行して DB にデータを用意しておきます.
続いて問い合せ用のクラス.

package study;
import java.util.Iterator;
import java.util.List;
import net.sf.hibernate.Session;

public class Query {
    public static void main(String[] args) {
        try {
            HibernateTemplate template = new HibernateTemplate();
            final List list = (List) template
                    .process(new HibernateCallback() {
                        public Object doProcess(Session session)
                                throws Exception {
                            return session.find("from study.Magazine");
                        }
                    });
            Iterator it = list.iterator();
            while (it.hasNext()) {
                Object o = it.next();
                if (o instanceof Object[]) {
                    Object[] a = (Object[]) o;
                    StringBuffer buf = new StringBuffer("[");
                    for (int i = 0; i < a.length; ++i) {
                        buf.append(a[i]).append(", ");
                    }
                    buf.setLength(buf.length() - 2);
                    buf.append("]");
                    System.out.println(buf);
                }
                else {
                    System.out.println(o);
                }
            }
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }
}

問い合せ結果が配列のリストで返ってくる場合があることも知っているので (苦笑),その場合は要素を表示するようにしています.
こいつを実行すると...(最後の表示のみ)

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

ということで,こいつをベースにHQL (太字部分) を変更しては実行するって感じで進めようと思います.


まずは「11.1. Case Sensitivity」.
HQL では,Java のクラス名やプロパティ名を除いて大文字・小文字は問わないそうです.
以上.


次は「11.2. The from clause」.
FROM 句にはエイリアスを付けることが出来ます.今まで知りませんでしたが,このエイリアスインスタンスを表すものらしいです.クラスではなくて.
そうかぁ.ちょっと納得.SELECT 句や WHERE 句で model.name ってやるときの気分は,Modelインスタンス (へのリファレンス) である modelname を参照している,ということだったわけかぁ.なぁるほどー.
FROM 句には複数のクラスをカンマ区切りで並べることも出来ます.
問い合せ文字列

from study.Magazine, Study.Model

を実行すると,

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

となりました.直積ですね.
いきなり配列で返ってきてる〜.


続いて「11.3. Associations and joins」です.
直積が欲しい場合なんて滅多にないわけで,Hibernate は豊富な結合をサポートしてくれてます.

  • inner join
  • left outer join
  • right outer join
  • full join

ということで,ひとつずつやってみますか.
まずは inner join.HQL における結合は,ANSI SQL と同様,FROM 句に記述します.ただし,結合するクラスを指定するのではなく,関連を指定します.こんな感じ

from study.Magazine magazine inner join magazine.model model

関連を参照するためには,雑誌にエイリアスを付けなくてはなりません.モデルの方にもエイリアスを付けていますが,こちらは問い合せ中で参照していないので,省略しても大丈夫です.
これを実行!!!!

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

関連づけされていないので,JJ と紗世ちゃんは表示されていません.


次に外部結合.まずは左から.

from study.Magazine magazine left outer join magazine.model model

その実行結果.

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

... これが左外部結合... なのか? んなわけない.
左外部結合だと,前の結果に モデルのいない JJ が増えるだけのはず.間違いない.
結果をみると,全ての雑誌に紗世ちゃんが組み合わされてます.なぜ?
Hibernate が作成した SQL は次のようになっています.

select 
    magazine0_.id as id0_, model1_.id as id1_, magazine0_.name as name0_, 
    magazine0_.publisher as publisher0_, 
    model1_.name as name1_, model1_.age as age1_ 
from 
    Magazine magazine0_ 
        left outer join Model model1_ on magazine0_.id=model1_.magazine

問題なさそう.
これを HSQLDB で問い合せしてみたところ,Hibernate での結果と同様,全ての雑誌と紗世ちゃんの組ができていました.
なので,これは Hibernate ではなく HSQLDB (1.7.2.3) の問題のようです.まいったな.
しょうがない,ここは無視するか...
次行きますよ! 次,次! (久々エビちゃん風)


ということで右外部結合.

from study.Magazine magazine right outer join magazine.model model

その実行結果.

java.sql.SQLException: Column not found: MODEL1_.ID in statement [
        select magazine0_.id as id0_, model1_.id as id1_, magazine0_.name as name0_, 
            magazine0_.publisher as publisher0_, model1_.name as name1_, model1_.age as age1_ 
        from Magazine magazine0_ right outer join Model model1_ on magazine0_.id=model1_.magazine]
    at org.hsqldb.jdbc.jdbcUtil.throwError(Unknown Source)
・・・

ぐはぁっ.(ToT)
エラーメッセージからは分かりにくいですが,HSQLDB がサポートしている外部結合は左だけなんですね.残念!!!!
念のため完全も.

from study.Magazine magazine full join magazine.model model

なんか,outer って書いてないのですが完全外部結合のことですよね? きっとそうですよね?
ともあれ,実行!!!!
...
やはりだめでした...
次行きますよ! 次,次!! (エビちゃん風)


次は fetch join です.おぉ,あの EJB 3.0 (Early Draft) に出てきたあれですか!? そうかぁ,あれも Hibernate からの流用品なんだぁ.
この fetch join は,親子関係にあるテーブルを結合する場合に使うことが出来ます.すると,問い合せ結果としては親だけが返されますが,関連づけられた子の方もすでにフェッチ済みというものです.
なので,普通に使うならこっちの方をデフォルトに考える方がよさげ.
こんな感じ.

from study.Magazine magazine left outer join fetch magazine.model

実行結果.

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

... これまたイメージと違うなぁ.
ちゃんと雑誌とモデルを問い合せしていますが,結果は配列になっていません (先頭に '[' が表示されていない).それは期待通り.しかし... 同じ雑誌を何度も返さないで欲しいなぁ.DISTINCT とか使えるのかなぁ? お,後から出てくるみたい.楽しみ〜♪
これがうまく使えると,旧来的な,出来るだけ一回の SELECT で多くの結果を得ようとする方法 (fetch join) と,Hibernate 的な小さな問い合せ + 遅延ロードをプログラムの修正なしで切り替えることが出来ます.HQL を外部化して名前付き問い合せを使わないといけませんが,SQL の出し方まで後々のチューニング要素に出来れば悪くないですよね.それだけに,惜しい... DISTINCT に期待.
なお,現在の実装では fetch join できるのは一つの関連だけみたいなことが書いてあります.さらに,scroll()iterate() では使えないとか,right outer join や full join では意味がないとか.いろいろ制限があるようですが,あまり気になる制限ではない感じ.


もう一ついっときましょう.「11.4. The select clause」です.
SELECT 句を使って,結果セット中のオブジェクトやプロパティを選択的に取得することも出来るとのことです.
ということで,まずはプロパティを取ってみます.

select magazine.name from study.Magazine magazine

その実行結果.

CanCam
JJ
ViVi
Ray

ふむ.


次はオブジェクトを取ってみます.

select model from study.Magazine magazine inner join magazine.model model

その実行結果.

Asami Usuda(19)
Yu Yamada(20)
Yuri Ebihara(24)
Jun Hasegawa(18)
Karina(20)

ふむ.
なお,永続オブジェクトの直接のプロパティだけでなく,プロパティがコンポーネント (値型) であれば,そのプロパティを取ってくるとかも出来るらしいです.コンポーネントかぁ.準備してなかったよ.残念!!!!


複数のプロパティ/オブジェクトを選択することも出来ます.その場合,結果は配列のリストになります.

select model.name, model.age from study.Model model

その実行結果.

[Sayo Aizawa, 26]
[Asami Usuda, 19]
[Yu Yamada, 20]
[Yuri Ebihara, 24]
[Jun Hasegawa, 18]
[Karina, 20]

結合していないので久々紗世ちゃん登場!!


さらに,Java のオブジェクトを生成して返すことも出来るそうです.これも EJB 3.0 (Early Draft) に出てたやつですね.
ということで,まずは適当なクラスを用意.

package study;

public class Pair {
    String first;
    String second;

    public Pair(String first, String second) {
        this.first = first;
        this.second = second;
    }
    public String toString() {
        return first + ":" + second;
    }
}

そして問い合せ.

select 
    new study.Pair(model.name, magazine.name) 
from 
    study.Magazine magazine inner join magazine.model model

その実行結果.

Asami Usuda:CanCam
Yu Yamada:CanCam
Yuri Ebihara:CanCam
Jun Hasegawa:ViVi
Karina:Ray

いいねぇ!! 紗世ちゃんいないけど.(;_;)
これを使えば,オブジェクトの配列を扱わずにすみます.結果セットでの配列なんて嫌いだもーん.
っていっても,この例の Pair は配列と大して変わらないというか存在意義がありませんが.
いいんです,ただの例ですから.
次行きますよ! 次,次!!(エビちゃん風)


嘘です,ごめんなさい.今日はここまでで勘弁してください.
それにしても HQL,なかなか高機能ですね.一通り試すには,HSQLDB では不足かも? (^^;