Hibernate 入門記 セッションその3 Query

前回に引き続き問い合せのお勉強です.今回は「9.3.2. The Query interface」へ進みます.
取得する結果セットの開始位置や最大行数を指定したい場合には,

  • Query

という interface を使うことができるようです.
こいつを取得するには,Session

    • Query createQuery(String)

を使用します.
Queryインスタンスを取得できれば,次のメソッドを呼び出すことで,フェッチする最初の行を指定することができます.

    • Query setFirstResult(int firstResult)

引数で渡すのは,0 からのオフセットです.戻り値は Query ということで自分自身ですね.
また,次のメソッドを呼び出すことで,フェッチする最大の行数を指定することができます.

    • Query setMaxResults(int maxResults)

まぁ,どうってことはないでしょう.
Query のプロパティを設定したら,実際に問い合せを行います.それには,Query

    • List list()

を呼び出します.あるいは,

    • Iterator iterate()

を使うこともできます.


Query を使うことでありがたいのは,問い合せ文字列を外部化できるということです.これは名前付き問い合せ (named query) と呼ばれるようです.
問い合せ文字列は,マッピングファイルに記述します.<hibernate-mapping> 要素の子として,<class> 要素などの後に

  • <query> 要素

を記述します.こいつは,次の属性を持っています.

name
この問い合せの名前を指定します.必須です.

問い合せの名前は,永続クラスの完全限定名で修飾することがお勧めのようです.たぶん...
そして,<query> 要素の内容として,問い合せ文字列を記述します.不等号記号を使うことが多いので,CDATA セクションを使うことを忘れないようにしましょう.
マッピングファイルに記述された問い合せ文字列を使うには,Session

    • Query getNamedQuery(String queryName)

を使います.引数はもちろん <query> 要素の name 属性で指定した名前です.


外部化したものであれ,していないものであれ,問い合せ文字列には,パラメータを含むこともできます.
パラメータは,名前のないものと名前付きのものがあります.
名前のないパラメータは,JDBC と同様に問い合せ文字列中に ? で記述します.このパラメータに値を設定するには,Query

    • Query setXxx(int position, xxx val)

を使います.
注意しなければならい点があります.第一引数の position は,JDBC API のように 1 からではなく,0 から始まります.間違いやすそうだなぁ.
ということで(?),Hibernate は名前付きのパラメータもサポートしています.
名前付きのパラメータは,問い合せ文字列中に :名前 という形式で指定します.この名前付きのパラメータに値を設定するには,Query

    • Query setXxx(String name, xxx val)

を使います.
名前付きパラメータを使うと,問い合せ文字列中にパラメータが現れる順番と無関係にパラメータを設定することができます.
また,問い合せ文字列中に同じ名前のパラメータを何度も使うこともできるようです.その場合でも,パラメータの設定は一回だけで済むはず.


ということで,ここまでの分をお試ししましょう.
今回は関連は不要なので,単純にモデルだけ使ってみます.

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

そしてモデルの永続クラス.

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;
    String magazine;

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

    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"/>
        <property name="magazine" access="field"/>
    </class>

    <query name="study.Model.byName"><![CDATA[
        from study.Model model where model.name = ?
    ]]></query>
    <query name="study.Model.byMagazine"><![CDATA[
        from study.Model model where model.magazine = :magazine
    ]]></query>
</hibernate-mapping>

二つの名前付き問い合せを記述しています.
こいつを hibernate.cfg.xml に記述します.
そして実行用のクラス.

package study;

import java.util.Iterator;

import net.sf.hibernate.Query;
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();

            Model yuri = new Model("Yuri Ebihara", "CanCam");
            session.save(yuri);
            Model yu = new Model("Yu Yamada", "CanCam");
            session.save(yu);
            Model moe = new Model("Moe Oshikiri", "CanCam");
            session.save(moe);
            Model asami = new Model("Asami Usuda", "CanCam");
            session.save(asami);
            Model sayo = new Model("Sayo Aizawa", "ViVi");
            session.save(sayo);
            Model jun = new Model("Jun Hasegawa", "ViVi");
            session.save(jun);
            Model karina = new Model("Karina", "Ray");
            session.save(karina);
            Model kana = new Model("Kana Ishida", "Ray");
            session.save(kana);

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

            session = factory.openSession();

            System.out.println("*** all ***");
            Query query = session.createQuery("from study.Model");
            query.setFirstResult(3);
            query.setMaxResults(4);
            Iterator it = query.iterate();
            while (it.hasNext()) {
                System.out.println(it.next());
            }

            System.out.println("*** byName ***");
            query = session.getNamedQuery("study.Model.byName");
            query.setString(0, "Yuri Ebihara");
            it = query.iterate();
            while (it.hasNext()) {
                System.out.println(it.next());
            }

            System.out.println("*** byMagazine ***");
            query = session.getNamedQuery("study.Model.byMagazine");
            query.setString("magazine", "CanCam");
            it = query.iterate();
            while (it.hasNext()) {
                System.out.println(it.next());
            }
        } catch (Throwable e) {
            e.printStackTrace();
        }
    }
}

ここでは,3 つの問い合せオブジェクトを作成しています.
最初の問い合せは,Session#createQuery(String) を使っています.問い合せ文字列自体は全件問い合せなのですが,オフセット 3 からの 4 件だけを取得します.
残りの二つの問い合せは,Session#getNamedQuery(String) で名前付き問い合せを使っています.
名前付き問い合せのうち,byName では名前なしのパラメータを,byMagazine では名前付きのパラメータを使っています.
こいつを実行!!!!

onSave() : Yuri Ebihara
onSave() : Yu Yamada
onSave() : Moe Oshikiri
onSave() : Asami Usuda
onSave() : Sayo Aizawa
onSave() : Jun Hasegawa
onSave() : Karina
onSave() : Kana Ishida
Hibernate: insert into Model (name, magazine, id) values (?, ?, ?)
*** all ***
Hibernate: select limit ? ? model0_.id as x0_0_ from Model model0_
Hibernate: select model0_.id as id0_, model0_.name as name0_, model0_.magazine as magazine0_ 
           from Model model0_ where model0_.id=?
onLoad() : Asami Usuda
Hibernate: select model0_.id as id0_, model0_.name as name0_, model0_.magazine as magazine0_ 
           from Model model0_ where model0_.id=?
onLoad() : Sayo Aizawa
Asami Usuda : CanCam
Hibernate: select model0_.id as id0_, model0_.name as name0_, model0_.magazine as magazine0_ 
           from Model model0_ where model0_.id=?
onLoad() : Jun Hasegawa
Sayo Aizawa : ViVi
Hibernate: select model0_.id as id0_, model0_.name as name0_, model0_.magazine as magazine0_ 
           from Model model0_ where model0_.id=?
onLoad() : Karina
Jun Hasegawa : ViVi
Karina : Ray
*** byName ***
Hibernate: select model0_.id as x0_0_ from Model model0_ where (model0_.name=? )
Hibernate: select model0_.id as id0_, model0_.name as name0_, model0_.magazine as magazine0_ 
           from Model model0_ where model0_.id=?
onLoad() : Yuri Ebihara
Yuri Ebihara : CanCam
*** byMagazine ***
Hibernate: select model0_.id as x0_0_ from Model model0_ where (model0_.magazine=? )
Hibernate: select model0_.id as id0_, model0_.name as name0_, model0_.magazine as magazine0_ 
           from Model model0_ where model0_.id=?
onLoad() : Yu Yamada
Yuri Ebihara : CanCam
Hibernate: select model0_.id as id0_, model0_.name as name0_, model0_.magazine as magazine0_ 
           from Model model0_ where model0_.id=?
onLoad() : Moe Oshikiri
Yu Yamada : CanCam
Moe Oshikiri : CanCam
Asami Usuda : CanCam

んー? うまくいった... のか?
問い合せ結果 (青字) そのものは悪くないようです.しかし... 問い合せ (太字) が多すぎるような?
よく見ると,まずはモデルの ID だけを問い合せて,その結果行ごとにもういちど問い合せを発行しているようです.なぜに??
Query#iterate() ってそういうもの? そんなバカなことしないでぇ〜.
...
ぐはぁっ,前回Session#iterate(String) でも同じ動きをしてるじゃないですか!! 気づかなかった... なぜこんなことを見逃したのかわからないし,全く記憶にないが,
相当頭が悪かったことは確かである。
過去の自分に幻滅。


すこし試行錯誤してみたところ,Query#iterate() ではなく Query#list() を使うと普通に問い合せしてくれるようです.
その実行結果 (問い合せ以降のみ).

*** all ***
Hibernate: select limit ? ? model0_.id as id, model0_.name as name, model0_.magazine as magazine 
           from Model model0_
onLoad() : Asami Usuda
onLoad() : Sayo Aizawa
onLoad() : Jun Hasegawa
onLoad() : Karina
Asami Usuda : CanCam
Sayo Aizawa : ViVi
Jun Hasegawa : ViVi
Karina : Ray
*** byName ***
Hibernate: select model0_.id as id, model0_.name as name, model0_.magazine as magazine 
           from Model model0_ where (model0_.name=? )
onLoad() : Yuri Ebihara
Yuri Ebihara : CanCam
*** byMagazine ***
Hibernate: select model0_.id as id, model0_.name as name, model0_.magazine as magazine 
           from Model model0_ where (model0_.magazine=? )
onLoad() : Yu Yamada
onLoad() : Moe Oshikiri
Yuri Ebihara : CanCam
Yu Yamada : CanCam
Moe Oshikiri : CanCam
Asami Usuda : CanCam

うん,この方がステキっ★
っていうか,Query#iterate() ってこういうものなわけ!?

The iterator will load objects on demand, using the identifiers returned by an initial SQL query (n+1 selects total).

むぎゅう,ちゃんと書いてあるし... おいらの目は節穴でした.ごめんなさい.m(__)m


ということで,基本はやっぱり List Session#find(String) または List Query#list() ということですね.
特別な理由がない限り,Iterator Session#iterate(String)Iterator Query#iterate() は使うべきではない,と.了解です〜.
ぢぐじょー,「ぐはぁっ」自体はお約束なのでいいとしても (いいのか?),一回遅れで「ぐはぁっ」するのは情けない.おかげで
カナリオチコミモード
…いわゆるKモードに入ってしまいまして,(いわないよ)
あーっもぉっ!!!!
もっと優秀な頭脳を我に!!