Hibernate 入門記 セッションその10 インターセプタ & メタデータ
さぁ,今日こそは 9 章を終わらせたいぞ!!
そんなわけで,まずは「9.9. Interceptors」です.
なんでも,セッションは様々なイベントを通知してくれるようです.しかも,そこでいろいろなことが出来るようです.
セッションからのイベントを受け取るには,
Interrceptor
という interface
を implements
します.その実装クラスを 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
というプロパティがあれば,その値の前後に ***
を付けています.
そしてこの前作ったばかりの HibernateTemplate
で Interceptor
を扱えるようにします.
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」は終了です.めでたしめでたし.
この後はトランザクションかぁ.パフォチューはまだまだ先ですね... 頑張ろ.