Hibernate 入門記 セッションその2 find(),iterate(),スカラー

今回は「9.3. Querying」です.
ほとんど毎回使ってきた問い合せです.一瞬,いまさら学習するまでもないかと思ったのですが,結構な分量があるみたい... 頑張ろ.


まずは基本となる問い合せメソッド,find() の仲間たちです.

    • List find(String query)
    • List find(String query, Object value, Type type)
    • List find(String query, Object[] values, Type[] types)

これまで使ってきたのは最初のやつだけですね.たぶん.これは,問い合せ文字列にパラメータが含まれていない場合に使います.
問い合せ文字列に1つだけパラメータが含まれていれば2番目,2つ以上のパラメータが含まれていれば3番目を使います.
2番目と3番目のメソッドで使われている Type というクラスは,Hibernate 型を表現するものです.基本的な型は,Hibernate というクラスに定数として定義されているので,それを使えばいいみたい.永続クラスの場合は,

Hibernate.entity(Cat.class)

みたいにするようです.


find() は結果を List で返します.しかし,結果セットの行数が非常に多い場合,リストを使われるとメモリの使用量が非常に多くなってしまう場合もあり得ます.そんな場合に使えるのが,

    • Iterator iterate(String query)
    • Iterator iterate(String query, Object value, Type type)
    • Iterator iterate(String query, Object[] values, Type[] types)

です.見ての通り,問い合せ結果を Iterator で返します.なんだ,いつも List#iterator() してましたがな.
問い合せ結果に含まれるオブジェクトの多くがすでにキャッシュに含まれていたり,結果セットに同じオブジェクトが何度も含まれる場合などは,find() よりも iterate() の方がパフォーマンスが良好になるとか.
ちょっと注意が必要なのは,Iterator の操作 (next() など) は checked な例外をスローすることができないこと.代わりに,LazyInitializationException という unchecked な例外でラップされるそうです.むしろ,この方が好都合な気がしますが.

08/09 追記
iterate() は,ID プロパティのみを問い合わせる SELECT 文と,フェッチの度に実行される SELECT 文とで,N+1 回の SELECT が実行されます.08/09 の入門記も参照してください.


続いて「9.3.1. Scalar queries」です.
スカラーってなんじゃい!? とりあえずここでは永続オブジェクトのプロパティまたは集計関数なんだと思っておけばいいようです.別に結果が1行で値 (カラム) も1つだとかいうわけではないみたい.
で,永続オブジェクトのプロパティや集計関数は,問い合せ (HQL) のSELECT 句に指定できるようです.その場合,問い合せ結果は SELECT 句に指定した並びの Object の配列になります.うーみゅ.
Object の配列かぁ.以前,外部結合を試したときにそうなりましたね.正直,あまりうれしくありません.なんで配列? まだマップの方がよくない? EJB 3.0Object の配列なんですよね.ResultSet より退化してどうするんだろう...


ともあれ,ここで一息入れましょう.お試しコーナーです.
なんか,いろいろ問い合せすればいいみたいなんで,いつものネタで.

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

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

雑誌の永続クラス.

package study;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;
import net.sf.hibernate.CallbackException;
import net.sf.hibernate.Lifecycle;
import net.sf.hibernate.Session;

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

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

    public void onLoad(Session s, Serializable id) {
        System.out.println("onLoad() : " + name);
    }
    public boolean onSave(Session s) throws CallbackException {
        System.out.println("onSave() : " + name);
        return false;
    }
    public boolean onUpdate(Session s) throws CallbackException {
        System.out.println("onUpdate() : " + name);
        return false;
    }
    public boolean onDelete(Session s) throws CallbackException {
        System.out.println("onDelete() : " + name);
        return false;
    }
}

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

<?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">
    <class name="Magazine">
        <id name="id" access="field" unsaved-value="-1">
            <generator class="increment"/>
        </id>
        <property name="name" access="field"/>
        <set name="model" access="field" cascade="all">
            <key column="MAGAZINE"/>
            <one-to-many class="Model"/>
        </set>
    </class>
</hibernate-mapping>

モデルの永続クラス.

package study;
import java.io.Serializable;
import net.sf.hibernate.CallbackException;
import net.sf.hibernate.Lifecycle;
import net.sf.hibernate.Session;

public class Model implements Lifecycle {
    int id = -1;
    String name;
    Magazine magazine;

    public Model() {
    }
    public Model(String name, Magazine magazine) {
        this.name = name;
        this.magazine = magazine;
        magazine.model.add(this);
    }
    public String toString() {
        return name + "(" + id + ")";
    }

    public void onLoad(Session s, Serializable id) {
        System.out.println("onLoad() : " + name);
    }
    public boolean onSave(Session s) throws CallbackException {
        System.out.println("onSave() : " + name);
        return false;
    }
    public boolean onUpdate(Session s) throws CallbackException {
        System.out.println("onUpdate() : " + name);
        return false;
    }
    public boolean onDelete(Session s) throws CallbackException {
        System.out.println("onDelete() : " + name);
        return false;
    }
}

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

<?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">
    <class name="Model">
        <id name="id" access="field" unsaved-value="-1">
            <generator class="increment"/>
        </id>
        <property name="name" access="field"/>
        <many-to-one name="magazine" access="field"/>
    </class>
</hibernate-mapping>

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

package study;

import java.util.Iterator;

import net.sf.hibernate.Hibernate;
import net.sf.hibernate.Session;
import net.sf.hibernate.SessionFactory;
import net.sf.hibernate.cfg.Configuration;

public class Main {
    public static void main(String[] args) {
        try {
            Configuration config = new Configuration();
            SessionFactory factory = config.configure().buildSessionFactory();

            Session session = factory.openSession();

            Magazine cancam = new Magazine("CanCam");
            session.save(cancam);
            Magazine vivi = new Magazine("ViVi");
            session.save(vivi);
            Magazine ray = new Magazine("Ray");
            session.save(ray);

            Model yuri = new Model("Yuri Ebihara", cancam);
            Model yu = new Model("Yu Yamada", cancam);
            Model moe = new Model("Moe Oshikiri", cancam);
            Model asami = new Model("Asami Usuda", cancam);
            Model sayo = new Model("Sayo Aizawa", vivi);
            Model jun = new Model("Jun Hasegawa", vivi);
            Model karina = new Model("Karina", ray);
            Model kana = new Model("Kana Ishida", ray);

            session.flush();
            session.connection().commit();

            session = factory.openSession();

            Iterator it = session.iterate(
                    "from study.Model model where model.magazine.name=?",
                    "CanCam", Hibernate.STRING);
            while (it.hasNext()) {
                System.out.println(it.next());
            }

            it = session.iterate("select magazine.name, count(magazine) "
                    + "from study.Magazine magazine inner join magazine.model "
                    + "group by magazine");
            while (it.hasNext()) {
                Object[] result = (Object[]) it.next();
                String name = (String) result[0];
                Integer count = (Integer) result[1];
                System.out.println(name + " : " + count);
            }
        }
        catch (Throwable e) {
            e.printStackTrace();
        }
    }
}

パラメータ付きの iterate() と,集計関数を使ってみました.そのためいつもよりモデルがたくさん♪
2番目の問い合せがちょっと不安ですが...
ともかく実行!!!!

 onSave() : CanCam
 onSave() : ViVi
 onSave() : Ray
 onSave() : Moe Oshikiri
 onSave() : Yuri Ebihara
 onSave() : Asami Usuda
 onSave() : Yu Yamada
 onSave() : Jun Hasegawa
 onSave() : Sayo Aizawa
 onSave() : Kana Ishida
 onSave() : Karina
 Hibernate: insert into Magazine (name, id) values (?, ?)
 Hibernate: insert into Model (name, magazine, id) values (?, ?, ?)
 Hibernate: update Model set MAGAZINE=? where id=?
 Hibernate: select model0_.id as x0_0_ from Model model0_, Magazine magazine1_ 
            where (magazine1_.name=?  and model0_.magazine=magazine1_.id)
 Hibernate: select model0_.id as id1_, model0_.name as name1_, 
                model0_.magazine as magazine1_, magazine1_.id as id0_, 
                magazine1_.name as name0_ 
            from Model model0_ left outer join 
                Magazine magazine1_ on model0_.magazine=magazine1_.id 
            where model0_.id=?
 onLoad() : CanCam
 onLoad() : Moe Oshikiri
 Hibernate: select model0_.id as id__, model0_.MAGAZINE as MAGAZINE__, 
                model0_.id as id1_, model0_.name as name1_, 
                model0_.magazine as magazine1_, magazine1_.id as id0_, 
                magazine1_.name as name0_ 
            from Model model0_ left outer join 
                Magazine magazine1_ on model0_.magazine=magazine1_.id 
            where model0_.MAGAZINE=?
 onLoad() : Yuri Ebihara
 onLoad() : Asami Usuda
 onLoad() : Yu Yamada
 Moe Oshikiri(1)
 Yuri Ebihara(2)
 Asami Usuda(3)
 Yu Yamada(4)
 Hibernate: select magazine0_.name as x0_0_, count(magazine0_.id) as x1_0_ 
            from Magazine magazine0_ inner join 
                Model model1_ on magazine0_.id=model1_.MAGAZINE 
            group by  magazine0_.id
 - SQL Error: -67, SQLState: 37000
 - Not in aggregate function or group by clause: 
 COLUMN MAGAZINE0_.NAME in statement [
         select magazine0_.name as x0_0_, count(magazine0_.id) as x1_0_ 
         from Magazine magazine0_ inner join Model model1_ on magazine0_.id=model1_.MAGAZINE 
         group by  magazine0_.id]
 - SQL Error: -67, SQLState: 37000
 - Not in aggregate function or group by clause: 
 COLUMN MAGAZINE0_.NAME in statement [
         select magazine0_.name as x0_0_, count(magazine0_.id) as x1_0_ 
         from Magazine magazine0_ inner join Model model1_ on magazine0_.id=model1_.MAGAZINE 
         group by  magazine0_.id]
 - Could not execute query
 java.sql.SQLException: Not in aggregate function or group by clause: 
     COLUMN MAGAZINE0_.NAME in statement [
         select magazine0_.name as x0_0_, count(magazine0_.id) as x1_0_ 
         from Magazine magazine0_ inner join Model model1_ on magazine0_.id=model1_.MAGAZINE 
         group by  magazine0_.id]
     at org.hsqldb.jdbc.jdbcUtil.throwError(Unknown Source)
     at org.hsqldb.jdbc.jdbcPreparedStatement.(Unknown Source)
     at org.hsqldb.jdbc.jdbcConnection.prepareStatement(Unknown Source)
     at net.sf.hibernate.impl.BatcherImpl.getPreparedStatement(BatcherImpl.java:249)
     at net.sf.hibernate.impl.BatcherImpl.getPreparedStatement(BatcherImpl.java:223)
     at net.sf.hibernate.impl.BatcherImpl.prepareQueryStatement(BatcherImpl.java:65)
     at net.sf.hibernate.loader.Loader.prepareQueryStatement(Loader.java:704)
     at net.sf.hibernate.hql.QueryTranslator.iterate(QueryTranslator.java:856)
     at net.sf.hibernate.impl.SessionImpl.iterate(SessionImpl.java:1608)
     at net.sf.hibernate.impl.SessionImpl.iterate(SessionImpl.java:1581)
     at net.sf.hibernate.impl.SessionImpl.iterate(SessionImpl.java:1573)
     at study.Main.main(Main.java:47)
 net.sf.hibernate.JDBCException: Could not execute query
     at net.sf.hibernate.impl.SessionImpl.iterate(SessionImpl.java:1611)
     at net.sf.hibernate.impl.SessionImpl.iterate(SessionImpl.java:1581)
     at net.sf.hibernate.impl.SessionImpl.iterate(SessionImpl.java:1573)
     at study.Main.main(Main.java:47)
 Caused by: java.sql.SQLException: Not in aggregate function or group by clause: 
 COLUMN MAGAZINE0_.NAME in statement [
         select magazine0_.name as x0_0_, count(magazine0_.id) as x1_0_ 
         from Magazine magazine0_ inner join Model model1_ on magazine0_.id=model1_.MAGAZINE 
         group by  magazine0_.id]
     at org.hsqldb.jdbc.jdbcUtil.throwError(Unknown Source)
     at org.hsqldb.jdbc.jdbcPreparedStatement.(Unknown Source)
     at org.hsqldb.jdbc.jdbcConnection.prepareStatement(Unknown Source)
     at net.sf.hibernate.impl.BatcherImpl.getPreparedStatement(BatcherImpl.java:249)
     at net.sf.hibernate.impl.BatcherImpl.getPreparedStatement(BatcherImpl.java:223)
     at net.sf.hibernate.impl.BatcherImpl.prepareQueryStatement(BatcherImpl.java:65)
     at net.sf.hibernate.loader.Loader.prepareQueryStatement(Loader.java:704)
     at net.sf.hibernate.hql.QueryTranslator.iterate(QueryTranslator.java:856)
     at net.sf.hibernate.impl.SessionImpl.iterate(SessionImpl.java:1608)
     ... 3 more

ぐはぁっ... うー,久しぶりに吐血すると気持ちいいかも? (^^;
それでいったい何が起こったわけ?
うーみゅ,最初の問い合せはうまくできているっぽい.
だめなのはやっぱり2番目ですね.なるほど,GROUP BY に Magazine を指定した場合,SELECT 句でそのプロパティは取れないわけですか.うーみゅ,SQL を見るとあまりにも当然なのですが,多少なりともオブジェクト指向な問い合わせ言語なら,これくらいできて欲しいような.まいっか.
ということで,2番目の問い合せを次のように修正.

            it = session.iterate("select magazine.name, count(magazine.name) "
                    + "from study.Magazine magazine inner join magazine.model "
                    + "group by magazine.name");

そして実行!!!!!! (2番目の問い合せ以降のみ抜粋)

 Hibernate: select magazine0_.name as x0_0_, count(magazine0_.name) as x1_0_ 
            from Magazine magazine0_ inner join 
                Model model1_ on magazine0_.id=model1_.MAGAZINE 
            group by  magazine0_.name
 CanCam : 4
 ViVi : 2
 Ray : 2

今度はうまくできました.やれやれ.


ところで最初の iterate() なんですが,2回 SQL を発行しています.雑誌を取得する SQL と,関連づけられたモデルを取得する SQL です.
注目は関連づけられたモデルを取得する SQL,なぜか外部結合が使われています.それだったら1回の SQL で済ませてくれればいいのに... と思ったりするわけですが.うーみゅ.
あと,やっぱり配列はやだなぁ.まぁ,SELECT 句を使わないようにすればいいのでしょうけれど.


ということで今回はここまで.
明日は「アスペクト指向技術セミナー」〜「ここだけオフ (真鍋かをりを語る会)」なので,次回は来週かな.