Hibernate 入門記 セッションその10 インターセプタ & メタデータ

さぁ,今日こそは 9 章を終わらせたいぞ!!
そんなわけで,まずは「9.9. Interceptors」です.
なんでも,セッションは様々なイベントを通知してくれるようです.しかも,そこでいろいろなことが出来るようです.
セッションからのイベントを受け取るには,

  • Interrceptor

という interfaceimplements します.その実装クラスを Session

    • Session openSession(Interceptor interceptor)
    • Session openSession(Connection connection, Interceptor interceptor)

に渡せばいいようです.
そしてその interceptor ですが,ずいぶんたくさんのメソッドが用意されています.うー,どうしようかな... しょうがない,面倒だけど見ていきますか.

    • boolean onLoad(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types)

永続オブジェクトがロードされる前に呼び出されます.
entity はコンストラクトされた直後のインスタンスです.
state は DB から読み出された値の配列です.この値を変更することで,永続オブジェクトのプロパティの値を変更することが出来ます.
state を変更した場合は,true を返します.

    • boolean onFlushDirty(Object entity, Serializable id, Object[] currentState, Object[] previousState, String[] propertyNames, Type[] types)

永続オブジェクトがフラッシュ (UPDATE) される (ダーティでなくなる) 前に呼び出されます.
currentState は永続オブジェクトのプロパティ値の配列です.この値を変更することで,DB に書き込まれる値を変更することが出来ます.
currentState を変更した場合は true を返します.

    • boolean onSave(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types)

まだ永続化されていないオブジェクトが保存 (INSERT) される前に呼び出されます.
state は永続オブジェクトのプロパティ値の配列です.この値を変更することで,DB に書き込まれる値を変更することが出来ます.
state を変更した場合は true を返します.

    • void onDelete(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types)

永続オブジェクトが削除される前に呼び出されます.
state は永続オブジェクトのプロパティ値の配列ですが,変更することはお勧めじゃないそうです.

    • void preFlush(Iterator entities)

セッションがフラッシュされる前に呼び出されます.

    • void postFlush(Iterator entities)

セッションがフラッシュされた後に呼び出されます.

    • Boolean isUnsaved(Object entity)

まだ永続化されていないオブジェクトが Session#saveOrUpdate() に渡された場合に呼び出されます.
戻り値 (プリミティブ型の boolean ではなく Object 型です) により,オブジェクトがどのように永続化されるかが決まります.
Boolean.TRUE を返すと,Session#save() に渡されます.
Boolean.FALSE を返すと,Session#update() に渡されます.
null を返すと Hibernate が unsaved-value (マッピングファイルで ID プロパティ に対して指定するやつです) に従って判断します.普段の動きですね.

    • int[] findDirty(Object entity, Serializable id, Object[] currentState, Object[] previousState, String[] propertyNames, Type[] types)

セッションがフラッシュされる前に,永続オブジェクトが「ダーティ」か決定するために呼び出されます.
戻り値は次のようになります.
永続オブジェクトがダーティな場合は,更新されたプロパティのインデックスを要素とする配列を返します.
永続オブジェクトがダーティでない場合は,空 (要素数が 0) の配列を返します.
Hibernate にダーティか決定してもらう場合は null を返します.

    • Object instantiate(Class clazz, Serializable id)

エンティティをインスタンス化する前に呼び出されます.
インターセプタでインスタンス化した場合は,それを返します.
null を返すと Hibernateインスタンス化します.
ふーん,実際に使う場面があまりイメージできませんが,いろいろなことが出来そうですね.


そして!! ついに 9 章のラスト,「9.10. Metadata API」へ進みます.やったね!!
Hibernate は自身のために永続オブジェクトのメタデータを構築しています.そいつをアプリからでも使えるようにしてくれているようです.
メタデータを表現するために,

  • ClassMetadata
  • CollectionMetadata

という interface が用意されています.前者は永続クラス,後者は関連のメタデータを表現するものですね.
んー,ClassMetadata ってメソッド 30 個くらいあるし... 全部見るのはつらい.(;_;)
ということでざっくりと眺めてみると...

    • プロパティの名前,型,値,それに NOT NULL かどうかとかなどを取得することが出来ます.
    • プロパティの値を設定することが出来ます.
    • ID プロパティがあるかチェックしたり,その名前,型,値を取得したり,設定することが出来ます.
    • 永続クラスが変更可能か,コールバックを実装しているかどうかなどを取得することが出来ます.

一方 CollectionMetadata は...

    • 関連の名前 (ロール名) を取得することが出来ます.
    • 関連端の型 (コレクションの要素型) を取得することが出来ます.
    • インデックスがあるかどうかや,インデックスの型を取得することが出来ます.
    • 遅延初期化するかどうかを取得することが出来ます.

などなど.
で,これらのメタデータを取得する方法ですが,またしても SessionFactory を使います.これまで全然気にしていなかったのに,いきなり大活躍ですね...
ともあれ,SessionFactory

    • ClassMetadata getClassMetadata(Class persistentClass)
    • CollectionMetadata getCollectionMetadata(String roleName)

を使ってメタデータを取得します.しかし... getCollectionMetadata(String) の引数,roleName がなんの名前かよく分からないのであった... 無念だ.
関連 (コレクション) のプロパティ名かなぁ? でもそれだけじゃ一意にならないし.永続クラス名を prefix にするのかなぁ?
おっ!! SessionFactory に次のメソッド発見!!!!

    • Map getAllClassMetadata()
    • Map getAllCollectionMetadata()

よしよし,こいつで全ての CollectionMetadata を取得して,そのロール名を調べればよさげ.


ということでお試ししましょう.
テーブルは前回と同じで.でもセッション編の最後なので (?) 一応掲載.

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

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

次に雑誌の永続クラスです.

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

public class Magazine {
    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 + model;
    }
}

今回は コールバック用の Lifecycle を実装していません.Hibernate とは無縁のクラスになりました.
その雑誌のマッピングファイル.

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

前回のと同じです.cascade="all-delete-orphan" を指定しています.
そしてモデルの永続クラス.

package study;

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

    public Model() {
    }

    public Model(String name) {
        this.name = name;
    }
    public String toString() {
        return name;
    }
}

こちらもコールバックの実装を外しました.
そのモデルのマッピングファイル.

<?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"/>
    </class>
</hibernate-mapping>

そして今回は Interceptor の実装クラスを用意します.

package study;
import java.io.Serializable;
import java.util.Iterator;
import net.sf.hibernate.CallbackException;
import net.sf.hibernate.Interceptor;
import net.sf.hibernate.type.Type;

public class InterceptorImpl implements Interceptor {
    public boolean onLoad(Object entity, Serializable id, Object[] state,
            String[] propertyNames, Type[] types) throws CallbackException {
        System.out.println("onLoad : " + entity.getClass().getName() + " id=" + id);
        for (int i = 0; i < state.length; ++i) {
            System.out.println("\t" + propertyNames[i] + "=" + state[i]);
        }
        System.out.println();
        return false;
    }
    public boolean onFlushDirty(Object entity, Serializable id, Object[] currentState,
            Object[] previousState, String[] propertyNames, Type[] types)
            throws CallbackException {
        System.out.println("onFlushDirty : " + entity);
        for (int i = 0; i < currentState.length; ++i) {
            System.out.println("\t" + propertyNames[i] + "=" + currentState[i]);
        }
        return false;
    }
    public boolean onSave(Object entity, Serializable id, Object[] state,
            String[] propertyNames, Type[] types) throws CallbackException {
        System.out.println("onSave : " + entity);
        for (int i = 0; i < state.length; ++i) {
            System.out.println("\t" + propertyNames[i] + "=" + state[i]);
            if ("name".equals(propertyNames[i])) {
                state[i] = "*** " + state[i] + " ***";
            }
        }
        System.out.println();
        return true;
    }
    public void onDelete(Object entity, Serializable id, Object[] state,
            String[] propertyNames, Type[] types) throws CallbackException {
        System.out.println("onDelete : " + entity);
        for (int i = 0; i < state.length; ++i) {
            System.out.println("\t" + propertyNames[i] + "=" + state[i]);
        }
        System.out.println();
    }
    public void preFlush(Iterator entities) throws CallbackException {
        System.out.println("preFlush");
        while (entities.hasNext()) {
            System.out.println("\t" + entities.next());
        }
    }
    public void postFlush(Iterator entities) throws CallbackException {
        System.out.println("postFlush");
        while (entities.hasNext()) {
            System.out.println("\t" + entities.next());
        }
    }
    public Boolean isUnsaved(Object entity) {
        System.out.println("isUnsaved : " + entity);
        return null;
    }
    public int[] findDirty(Object entity, Serializable id, Object[] currentState,
            Object[] previousState, String[] propertyNames, Type[] types) {
        System.out.println("findDirty : " + entity);
        return null;
    }
    public Object instantiate(Class clazz, Serializable id) throws CallbackException {
        System.out.println("instantiate : " + clazz.getName() + " id=" + id);
        return null;
    }

}

ちょっと長ったらしいですが,ほとんど表示しかしていません.
唯一,onSave()name というプロパティがあれば,その値の前後に *** を付けています.
そしてこの前作ったばかりの HibernateTemplateInterceptor を扱えるようにします.

package study;
import net.sf.hibernate.HibernateException;
import net.sf.hibernate.Interceptor;
import net.sf.hibernate.Session;
import net.sf.hibernate.SessionFactory;
import net.sf.hibernate.Transaction;
import net.sf.hibernate.cfg.Configuration;

public class HibernateTemplate {
    SessionFactory factory;
    Interceptor interceptor;

    public HibernateTemplate() throws HibernateException {
        this(new Configuration());
    }
    public HibernateTemplate(Configuration config) throws HibernateException {
        factory = config.configure().buildSessionFactory();
    }
    public HibernateTemplate(Interceptor interceptor) throws HibernateException {
        this();
        this.interceptor = interceptor;
    }
    public HibernateTemplate(Configuration config, Interceptor interceptor)
            throws HibernateException {
        this(config);
        factory = config.configure().buildSessionFactory();
    }

    public Object process(HibernateCallback callback) throws Exception {
        Session session = interceptor == null ? factory.openSession() : factory
                .openSession(interceptor);
        try {
            Transaction tx = session.beginTransaction();
            try {
                Object result = callback.doProcess(session);
                tx.commit();
                return result;
            }
            catch (Exception e) {
                tx.rollback();
                throw e;
            }
        }
        finally {
            session.close();
        }
    }
}

最後に実行用のクラス.

package study;
import java.util.Iterator;
import net.sf.hibernate.Session;
import net.sf.hibernate.SessionFactory;
import net.sf.hibernate.metadata.ClassMetadata;
import net.sf.hibernate.metadata.CollectionMetadata;
import net.sf.hibernate.type.Type;

public class Main {
    public static void main(String[] args) {
        try {
            HibernateTemplate template = new HibernateTemplate(new InterceptorImpl());
            SessionFactory factory = template.factory;

            System.out.println("*** class metadata ***");
            Iterator it = factory.getAllClassMetadata().values().iterator();
            while (it.hasNext()) {
                ClassMetadata metadata = (ClassMetadata) it.next();
                System.out.println(metadata.getMappedClass().getName());
                String[] names = metadata.getPropertyNames();
                Type[] types = metadata.getPropertyTypes();
                for (int i = 0; i < names.length; ++i) {
                    System.out.println("\t" + names[i] + " : " + types[i].getName());
                }
            }

            System.out.println("\n*** collection metadata ***");
            it = factory.getAllCollectionMetadata().values().iterator();
            while (it.hasNext()) {
                CollectionMetadata metadata = (CollectionMetadata) it.next();
                System.out.println(metadata.getRole());
                System.out.println("\tElementType : "
                        + metadata.getElementType().getName());
                System.out.println("\tKeyType : " + metadata.getKeyType().getName());
                if (metadata.hasIndex()) {
                    System.out.println("\tIndexType : "
                            + metadata.getIndexType().getName());
                }
            }

            System.out.println("\n*** create magazine ***");
            template.process(new HibernateCallback() {
                public Object doProcess(Session session) throws Exception {
                    session.save(new Magazine("CanCam"));
                    session.save(new Magazine("ViVi"));
                    return null;
                }
            });

            System.out.println("\n*** create model ***");
            template.process(new HibernateCallback() {
                public Object doProcess(Session session) throws Exception {
                    Magazine cancam = (Magazine) session.get(Magazine.class, new Integer(
                            0));
                    cancam.model.add(new Model("Yuri Ebihara"));
                    cancam.model.add(new Model("Asami Usuda"));

                    Magazine vivi = (Magazine) session
                            .get(Magazine.class, new Integer(1));
                    cancam.model.add(new Model("Sayo Aizawa"));
                    cancam.model.add(new Model("Jun Hasegawa"));
                    return null;
                }
            });

            System.out.println("\n*** clear association ***");
            template.process(new HibernateCallback() {
                public Object doProcess(Session session) throws Exception {
                    Iterator it = session.find("from study.Magazine magazin").iterator();
                    while (it.hasNext()) {
                        ((Magazine) it.next()).model.clear();
                    }
                    return null;
                }
            });

            System.out.println("\n*** print ***");
            template.process(new HibernateCallback() {
                public Object doProcess(Session session) throws Exception {
                    Iterator it = session.find("from study.Model").iterator();
                    while (it.hasNext()) {
                        System.out.println(it.next());
                    }
                    return null;
                }
            });
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }
}

基本的にやっていることは前回と同じですが,はじめのところでメタデータを表示しています.
あと,途中のプリントは削除しました.
こいつを実行!!!!

*** class metadata ***
study.Magazine
    name : string
    model : java.util.Set
study.Model
    name : string

*** collection metadata ***
study.Magazine.model
    ElementType : study.Model
    KeyType : integer

*** create magazine ***
onSave : CanCam
    name=CanCam
    model=

Hibernate: insert into Magazine (name, id) values (?, null)
Hibernate: call identity()
onSave : ViVi
    name=ViVi
    model=

Hibernate: insert into Magazine (name, id) values (?, null)
Hibernate: call identity()
preFlush
    *** CanCam ***
    *** ViVi ***
findDirty : *** CanCam ***
findDirty : *** ViVi ***
postFlush
    *** CanCam ***
    *** ViVi ***

*** create model ***
Hibernate: select magazine0_.id as id0_, magazine0_.name as name0_ 
           from Magazine magazine0_ where magazine0_.id=?
instantiate : study.Magazine id=0
onLoad : study.Magazine id=0
    name=*** CanCam ***
Hibernate: select model0_.magazine as magazine__, model0_.id as id__, 
               model0_.id as id0_, model0_.name as name0_ 
           from Model model0_ where model0_.magazine=?
    model=

Hibernate: select magazine0_.id as id0_, magazine0_.name as name0_ 
           from Magazine magazine0_ where magazine0_.id=?
instantiate : study.Magazine id=1
onLoad : study.Magazine id=1
    name=*** ViVi ***
Hibernate: select model0_.magazine as magazine__, model0_.id as id__, 
               model0_.id as id0_, model0_.name as name0_ 
           from Model model0_ where model0_.magazine=?
    model=

preFlush
    *** CanCam ***[Asami Usuda, Yuri Ebihara, Jun Hasegawa, Sayo Aizawa]
    *** ViVi ***
isUnsaved : Asami Usuda
onSave : Asami Usuda
    name=Asami Usuda

Hibernate: insert into Model (name, id) values (?, null)
Hibernate: call identity()
isUnsaved : Yuri Ebihara
onSave : Yuri Ebihara
    name=Yuri Ebihara

Hibernate: insert into Model (name, id) values (?, null)
Hibernate: call identity()
isUnsaved : Jun Hasegawa
onSave : Jun Hasegawa
    name=Jun Hasegawa

Hibernate: insert into Model (name, id) values (?, null)
Hibernate: call identity()
isUnsaved : Sayo Aizawa
onSave : Sayo Aizawa
    name=Sayo Aizawa

Hibernate: insert into Model (name, id) values (?, null)
Hibernate: call identity()
findDirty : *** CanCam ***[*** Asami Usuda ***, *** Yuri Ebihara ***, 
                *** Jun Hasegawa ***, *** Sayo Aizawa ***]
findDirty : *** ViVi ***
findDirty : *** Asami Usuda ***
findDirty : *** Yuri Ebihara ***
findDirty : *** Jun Hasegawa ***
findDirty : *** Sayo Aizawa ***
Hibernate: update Model set magazine=? where id=?
Hibernate: update Model set magazine=? where id=?
Hibernate: update Model set magazine=? where id=?
Hibernate: update Model set magazine=? where id=?
postFlush
    *** Yuri Ebihara ***
    *** Asami Usuda ***
    *** CanCam ***[*** Asami Usuda ***, *** Yuri Ebihara ***, 
            *** Jun Hasegawa ***, *** Sayo Aizawa ***]
    *** Sayo Aizawa ***
    *** ViVi ***
    *** Jun Hasegawa ***

*** clear association ***
preFlush
Hibernate: select magazine0_.id as id, magazine0_.name as name from Magazine magazine0_
instantiate : study.Magazine id=0
instantiate : study.Magazine id=1
onLoad : study.Magazine id=0
    name=*** CanCam ***
Hibernate: select model0_.magazine as magazine__, model0_.id as id__, 
               model0_.id as id0_, model0_.name as name0_ 
           from Model model0_ where model0_.magazine=?
instantiate : study.Model id=0
instantiate : study.Model id=1
instantiate : study.Model id=2
instantiate : study.Model id=3
onLoad : study.Model id=0
    name=*** Asami Usuda ***

onLoad : study.Model id=1
    name=*** Yuri Ebihara ***

onLoad : study.Model id=2
    name=*** Jun Hasegawa ***

onLoad : study.Model id=3
    name=*** Sayo Aizawa ***

    model=[*** Asami Usuda ***, *** Sayo Aizawa ***, 
            *** Yuri Ebihara ***, *** Jun Hasegawa ***]

onLoad : study.Magazine id=1
    name=*** ViVi ***
Hibernate: select model0_.magazine as magazine__, model0_.id as id__, 
               model0_.id as id0_, model0_.name as name0_ 
           from Model model0_ where model0_.magazine=?
    model=

preFlush
    *** Yuri Ebihara ***
    *** Asami Usuda ***
    *** CanCam ***
    *** Sayo Aizawa ***
    *** ViVi ***
    *** Jun Hasegawa ***
onDelete : *** Asami Usuda ***
    name=*** Asami Usuda ***

onDelete : *** Jun Hasegawa ***
    name=*** Jun Hasegawa ***

onDelete : *** Sayo Aizawa ***
    name=*** Sayo Aizawa ***

onDelete : *** Yuri Ebihara ***
    name=*** Yuri Ebihara ***

findDirty : *** CanCam ***
findDirty : *** ViVi ***
findDirty : *** Asami Usuda ***
findDirty : *** Yuri Ebihara ***
findDirty : *** Jun Hasegawa ***
findDirty : *** Sayo Aizawa ***
Hibernate: update Model set magazine=null where magazine=?
Hibernate: delete from Model where id=?
Hibernate: delete from Model where id=?
Hibernate: delete from Model where id=?
Hibernate: delete from Model where id=?
postFlush
    *** CanCam ***
    *** ViVi ***

*** print ***
preFlush
Hibernate: select model0_.id as id, model0_.name as name from Model model0_
preFlush
postFlush

あまりに長いので今日は色つけなしで.残念!!!!
ともあれ,なんかうまくいったぽいです.
関連のロール名ですが,クラスの完全限定名にプロパティ名をピリオドでつなげたものですね.なーんだ,それだけかぁ.
最後の print のセッションを見ると,SELECT を実行する前にフラッシュしようとしていますね.これはちょっと前に見たフラッシュモードの説明と一致しています.SELECT の後にもフラッシュしようとしているのは,トランザクションがコミットされるからで,これもフラッシュモードの説明と一致しています.
ようやく Hibernate の動きが見えてきた感じでこれはいいかも♪


ということで,「9. Manipulating Persistent Data」は終了です.めでたしめでたし.
この後はトランザクションかぁ.パフォチューはまだまだ先ですね... 頑張ろ.