275 lines
12 KiB
XML
275 lines
12 KiB
XML
<?xml version="1.0" encoding="Shift_JIS"?>
|
||
<chapter id="events">
|
||
<title>インターセプタとイベント</title>
|
||
|
||
<para>
|
||
アプリケーションがHibernateの内部で発生するイベントに対応できると役に立つことがあります。
|
||
ある種の一般的な機能を実装できるようになり、
|
||
またHibernateの機能を拡張することもできるようになります。
|
||
</para>
|
||
|
||
<sect1 id="objectstate-interceptors" revision="3">
|
||
<title>インターセプタ</title>
|
||
|
||
<para>
|
||
<literal>Interceptor</literal> インターフェイスを使って、
|
||
セッションからアプリケーションへコールバックをすることができます。
|
||
これにより永続オブジェクトの保存、更新、削除、読み込みの前に、
|
||
アプリケーションがプロパティを検査したり操作したりできるようになります。
|
||
これは監査情報の追跡に利用できます。
|
||
下の例で <literal>Interceptor</literal> は <literal>Auditable</literal>
|
||
が作成されると自動的に <literal>createTimestamp</literal> を設定し、
|
||
<literal>Auditable</literal> が更新されると自動的に
|
||
<literal>lastUpdateTimestamp</literal> プロパティを更新します。
|
||
</para>
|
||
|
||
<para>
|
||
<literal>Interceptor</literal> を直接実装したり、
|
||
(さらによいのは)<literal>EmptyInterceptor</literal> を拡張したりできます。
|
||
</para>
|
||
|
||
<programlisting><![CDATA[package org.hibernate.test;
|
||
|
||
import java.io.Serializable;
|
||
import java.util.Date;
|
||
import java.util.Iterator;
|
||
|
||
import org.hibernate.EmptyInterceptor;
|
||
import org.hibernate.Transaction;
|
||
import org.hibernate.type.Type;
|
||
|
||
public class AuditInterceptor extends EmptyInterceptor {
|
||
|
||
private int updates;
|
||
private int creates;
|
||
private int loads;
|
||
|
||
public void onDelete(Object entity,
|
||
Serializable id,
|
||
Object[] state,
|
||
String[] propertyNames,
|
||
Type[] types) {
|
||
// do nothing
|
||
}
|
||
|
||
public boolean onFlushDirty(Object entity,
|
||
Serializable id,
|
||
Object[] currentState,
|
||
Object[] previousState,
|
||
String[] propertyNames,
|
||
Type[] types) {
|
||
|
||
if ( entity instanceof Auditable ) {
|
||
updates++;
|
||
for ( int i=0; i < propertyNames.length; i++ ) {
|
||
if ( "lastUpdateTimestamp".equals( propertyNames[i] ) ) {
|
||
currentState[i] = new Date();
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
public boolean onLoad(Object entity,
|
||
Serializable id,
|
||
Object[] state,
|
||
String[] propertyNames,
|
||
Type[] types) {
|
||
if ( entity instanceof Auditable ) {
|
||
loads++;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
public boolean onSave(Object entity,
|
||
Serializable id,
|
||
Object[] state,
|
||
String[] propertyNames,
|
||
Type[] types) {
|
||
|
||
if ( entity instanceof Auditable ) {
|
||
creates++;
|
||
for ( int i=0; i<propertyNames.length; i++ ) {
|
||
if ( "createTimestamp".equals( propertyNames[i] ) ) {
|
||
state[i] = new Date();
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
public void afterTransactionCompletion(Transaction tx) {
|
||
if ( tx.wasCommitted() ) {
|
||
System.out.println("Creations: " + creates + ", Updates: " + updates, "Loads: " + loads);
|
||
}
|
||
updates=0;
|
||
creates=0;
|
||
loads=0;
|
||
}
|
||
|
||
}]]></programlisting>
|
||
|
||
<para>
|
||
インターセプタには二種類あります:
|
||
<literal>Session</literal> スコープのものと
|
||
<literal>SessionFactory</literal> スコープのものです。
|
||
</para>
|
||
|
||
<para>
|
||
<literal>Session</literal> スコープのインターセプタは、
|
||
セッションをオープンするときに指定します。
|
||
<literal>Interceptor</literal> を引数に取るSessionFactory.openSession()
|
||
のオーバーロードメソッドの一つを使います。
|
||
</para>
|
||
|
||
<programlisting><![CDATA[Session session = sf.openSession( new AuditInterceptor() );]]></programlisting>
|
||
|
||
<para>
|
||
<literal>SessionFactory</literal> スコープのインターセプタは <literal>Configuration</literal>
|
||
オブジェクトを使って登録します。
|
||
これは <literal>SessionFactory</literal> の構築よりも優先されます。
|
||
この場合、提供されるインターセプタは <literal>SessionFactory</literal>
|
||
からオープンされたすべてのセッションに適用されます。
|
||
これは使用するインターセプタを明示的に指定してセッションをオープンしない限り、そうなります。
|
||
<literal>SessionFactory</literal> スコープのインターセプタはスレッドセーフでなければなりません。
|
||
複数のセッションが(潜在的に)このインターセプタを同時並行で使用することになるため、
|
||
セッション固有の状態を格納しないように気をつけてください。
|
||
</para>
|
||
|
||
<programlisting><![CDATA[new Configuration().setInterceptor( new AuditInterceptor() );]]></programlisting>
|
||
|
||
</sect1>
|
||
|
||
<sect1 id="objectstate-events" revision="4">
|
||
<title>イベントシステム</title>
|
||
|
||
<para>
|
||
永続化層で特定のイベントに対応しなければならない場合、
|
||
Hibernate3の <emphasis>イベント</emphasis> アーキテクチャを使うこともできます。
|
||
イベントシステムはインターセプタと一緒に使うか、またはインターセプタの代わりとして使うことができます。
|
||
</para>
|
||
|
||
<para>
|
||
本質的に <literal>Session</literal> インターフェイスのすべてのメソッドは、
|
||
1個のイベントと相互に関連します。
|
||
例えば <literal>LoadEvent</literal>、<literal>FlushEvent</literal> などがあります
|
||
(定義済みのイベント型の一覧については、XML設定ファイルのDTDや
|
||
<literal>org.hibernate.event</literal> パッケージを調べてください)。
|
||
リクエストがこれらのメソッドの1つから作られるとき、
|
||
Hibernateの <literal>Session</literal> は適切なイベントを生成し、
|
||
そのイベント型に設定されたイベントリスナに渡します。
|
||
すばらしいことに、これらのリスナはそのメソッドと同じ処理を実装します。
|
||
とはいえ、リスナインターフェイスの一つを自由にカスタム実装できます
|
||
(つまり、<literal>LoadEvent</literal> は登録された <literal>LoadEventListener</literal>
|
||
インターフェイスの実装により処理されます)。
|
||
その場合、その実装には <literal>Session</literal> から作られたどのような <literal>load()</literal>
|
||
リクエストをも処理する責任があります。
|
||
</para>
|
||
|
||
<para>
|
||
リスナは事実上シングルトンであると見なせます。
|
||
つまりリスナはリクエスト間で共有されるため、
|
||
インスタンス変数として状態を保持するべきではないということです。
|
||
</para>
|
||
|
||
<para>
|
||
カスタムリスナは処理したいイベントについて適切なインターフェイスを実装するべきです。
|
||
便利な基底クラスのうちの一つを継承してもよいです
|
||
(またはHibernateがデフォルトで使用するイベントリスナを継承してもよいです。
|
||
すばらしいことに、この目的のために非finalとして宣言されています)。
|
||
カスタムリスナは <literal>Configuration</literal> オブジェクトを使ってプログラムから登録するか、
|
||
HibernateのXML設定ファイルで指定できます
|
||
(プロパティファイルで宣言的に設定する方法はサポートされていません)。
|
||
カスタムロードイベントリスナの例を示します。
|
||
</para>
|
||
|
||
<programlisting><![CDATA[public class MyLoadListener implements LoadEventListener {
|
||
// this is the single method defined by the LoadEventListener interface
|
||
public void onLoad(LoadEvent event, LoadEventListener.LoadType loadType)
|
||
throws HibernateException {
|
||
if ( !MySecurity.isAuthorized( event.getEntityClassName(), event.getEntityId() ) ) {
|
||
throw MySecurityException("Unauthorized access");
|
||
}
|
||
}
|
||
}]]></programlisting>
|
||
|
||
<para>
|
||
デフォルトリスナ以外のリスナを使うには、Hibernateへの設定も必要です:
|
||
</para>
|
||
|
||
<programlisting><![CDATA[<hibernate-configuration>
|
||
<session-factory>
|
||
...
|
||
<event type="load">
|
||
<listener class="com.eg.MyLoadListener"/>
|
||
<listener class="org.hibernate.event.def.DefaultLoadEventListener"/>
|
||
</event>
|
||
</session-factory>
|
||
</hibernate-configuration>]]></programlisting>
|
||
|
||
<para>
|
||
またその他に、プログラムで登録する方法もあります:
|
||
</para>
|
||
|
||
<programlisting><![CDATA[Configuration cfg = new Configuration();
|
||
LoadEventListener[] stack = { new MyLoadListener(), new DefaultLoadEventListener() };
|
||
cfg.EventListeners().setLoadEventListeners(stack);]]></programlisting>
|
||
|
||
<para>
|
||
リスナを宣言的に登録すると、そのリスナのインスタンスを共有できません。
|
||
複数の <literal><listener/></literal> 要素で同じクラス名が使われると、
|
||
それぞれの参照はそのクラスの別々のインスタンスを指すことになります。
|
||
リスナ型の間でリスナインスタンスを共有する必要があれば、
|
||
プログラムで登録する方法を採らなければなりません。
|
||
</para>
|
||
|
||
<para>
|
||
なぜインターフェイスを実装して、特化した型を設定時に指定するのでしょうか?
|
||
リスナの実装クラスに、複数のイベントリスナインターフェイスを実装できるからです。
|
||
登録時に追加で型を指定することで、カスタムリスナのon/offを設定時に簡単に切り替えられます。
|
||
</para>
|
||
|
||
</sect1>
|
||
|
||
<sect1 id="objectstate-decl-security" revision="2">
|
||
<title>Hibernateの宣言的なセキュリティ</title>
|
||
<para>
|
||
一般的にHibernateアプリケーションの宣言的なセキュリティは、セッションファサード層で管理します。
|
||
現在、Hiberenate3はJACCで許可しかつ、JAASで認証したアクションを許しています。
|
||
これはイベントアーキテクチャの最上位に組み込まれているオプションの機能です。
|
||
</para>
|
||
|
||
<para>
|
||
まず最初に、適切なイベントリスナを設定してJAAS認証を使えるようにしなければなりません。
|
||
</para>
|
||
|
||
<programlisting><![CDATA[<listener type="pre-delete" class="org.hibernate.secure.JACCPreDeleteEventListener"/>
|
||
<listener type="pre-update" class="org.hibernate.secure.JACCPreUpdateEventListener"/>
|
||
<listener type="pre-insert" class="org.hibernate.secure.JACCPreInsertEventListener"/>
|
||
<listener type="pre-load" class="org.hibernate.secure.JACCPreLoadEventListener"/>]]></programlisting>
|
||
|
||
<para>
|
||
特定のイベント型に対してちょうど一つのリスナがあるとき、
|
||
<literal><listener type="..." class="..."/></literal>
|
||
は <literal><event type="..."><listener class="..."/></event></literal>
|
||
の簡略形に過ぎないことに注意してください。
|
||
</para>
|
||
|
||
<para>
|
||
次に、同じく <literal>hibernate.cfg.xml</literal> でロールにパーミッションを与えてください:
|
||
</para>
|
||
|
||
<programlisting><![CDATA[<grant role="admin" entity-name="User" actions="insert,update,read"/>
|
||
<grant role="su" entity-name="User" actions="*"/>]]></programlisting>
|
||
|
||
<para>
|
||
このロール名は使用するJACCプロバイダに理解されるロールです。
|
||
</para>
|
||
|
||
</sect1>
|
||
|
||
</chapter>
|
||
|