Hibernate.org Community Documentation

- 前のページ
- 次のページ

## 第14章 インターセプタとイベント

アプリケーションが Hibernate の内部で発生するイベントに対応できると役に立つことがあります。ある種の一般的な機能を実装できるようになり、また Hibernate の機能を拡張することもできるようになります。

## 14.1. インターセプタ

`Interceptor` インターフェースを使って、セッションからアプリケーションへコールバックをすることができます。これにより永続オブジェクトの保存、更新、削除、読み込みの前に、アプリケーションがプロパティを検査したり操作したりできるようになります。これは監査情報の追跡に利用できます。下の例で `Interceptor` は `Auditable` が作成されると自動的に `createTimestamp` を設定し、 `Auditable` が更新されると自動的に `lastUpdateTimestamp` プロパティを更新します。

`Interceptor` を直接実装したり、 （さらによいのは） `EmptyInterceptor` を拡張したりできます。

    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;

        }

    }

インターセプタには二種類あります： `Session` スコープのものと `SessionFactory` スコープのものです。

`Session` スコープのインターセプタは、セッションをオープンするときに指定します。 `Interceptor` を引数に取る SessionFactory.openSession() のオーバーロードメソッドの一つを使います。

    Session session = sf.openSession( new AuditInterceptor() );

`SessionFactory` スコープのインターセプタは `Configuration` オブジェクトを使って登録します。これは `SessionFactory` の構築よりも優先されます。この場合、提供されるインターセプタは `SessionFactory` からオープンされたすべてのセッションに適用されます。これは使用するインターセプタを明示的に指定してセッションをオープンしない限り、そうなります。 `SessionFactory` スコープのインターセプタはスレッドセーフでなければなりません。複数のセッションが （潜在的に） このインターセプタを同時並行で使用することになるため、セッション固有の状態を格納しないように気をつけてください。

    new Configuration().setInterceptor( new AuditInterceptor() );

## 14.2. イベントシステム

永続化層で特定のイベントに対応しなければならない場合、 Hibernate3 の イベント アーキテクチャを使うこともできます。イベントシステムはインターセプタと一緒に使うか、またはインターセプタの代わりとして使うことができます。

本質的に `Session` インターフェースのすべてのメソッドは、1個のイベントと相互に関連します。例えば `LoadEvent` 、 `FlushEvent` などがあります （定義済みのイベント型の一覧については、 XML 設定ファイルの DTD や `org.hibernate.event` パッケージを調べてください） 。リクエストがこれらのメソッドの1つから作られるとき、 Hibernate の `Session` は適切なイベントを生成し、そのイベント型に設定されたイベントリスナに渡します。すばらしいことに、これらのリスナはそのメソッドと同じ処理を実装します。とはいえ、リスナインターフェースの一つを自由にカスタム実装できます （つまり、 `LoadEvent` は登録された `LoadEventListener` インターフェースの実装により処理されます）。その場合、その実装には `Session` から作られたどのような `load()` リクエストをも処理する責任があります。

リスナは事実上シングルトンであると見なせます。つまりリスナはリクエスト間で共有されるため、インスタンス変数として状態を保持するべきではないということです。

カスタムリスナは処理したいイベントについて適切なインターフェースを実装するべきです。便利な基底クラスのうちの一つを継承してもよいです （または Hibernate がデフォルトで使用するイベントリスナを継承してもよいです。すばらしいことに、この目的のために非 final として宣言されています） 。カスタムリスナは `Configuration` オブジェクトを使ってプログラムから登録するか、 Hibernate の XML 設定ファイルで指定できます （プロパティファイルで宣言的に設定する方法はサポートされていません） 。カスタムロードイベントリスナの例を示します:

    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");

            }

        }

    }

デフォルトリスナ以外のリスナを使うには、 Hibernate への設定も必要です：

    <hibernate-configuration>

        <session-factory>

            ...

            <event type="load">

                <listener class="com.eg.MyLoadListener"/>

                <listener class="org.hibernate.event.def.DefaultLoadEventListener"/>

            </event>

        </session-factory>

    </hibernate-configuration

    >

またその他に、プログラムで登録する方法もあります：

    Configuration cfg = new Configuration();

    LoadEventListener[] stack = { new MyLoadListener(), new DefaultLoadEventListener() };

    cfg.EventListeners().setLoadEventListeners(stack);

リスナを宣言的に登録すると、そのリスナのインスタンスを共有できません。複数の `<listener/>` 要素で同じクラス名が使われると、それぞれの参照はそのクラスの別々のインスタンスを指すことになります。リスナ型の間でリスナインスタンスを共有する必要があれば、プログラムで登録する方法を採らなければなりません。

なぜインターフェースを実装して、特化した型を設定時に指定するのでしょうか？リスナの実装クラスに、複数のイベントリスナインターフェースを実装できるからです。登録時に追加で型を指定することで、カスタムリスナの on/off を設定時に簡単に切り替えられます。

## 14.3. Hibernate の宣言的なセキュリティ

一般的に Hibernate アプリケーションの宣言的なセキュリティは、セッションファサード層で管理します。現在、 Hiberenate3 は JACC で許可し、さらに JAAS で認証したアクションを許しています。これはイベントアーキテクチャの最上位に組み込まれているオプションの機能です。

まず最初に、適切なイベントリスナを設定して JAAS 認証を使えるようにしなければなりません。

    <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"/>

特定のイベント型に対してちょうど一つのリスナがあるとき、 `<listener type="..." class="..."/>` は `<event type="..."><listener class="..."/></event>` の簡略形に過ぎないことに注意してください。

次に、同じく `hibernate.cfg.xml` でロールにパーミッションを与えてください：

    <grant role="admin" entity-name="User" actions="insert,update,read"/>

    <grant role="su" entity-name="User" actions="*"/>

このロール名は使用する JACC プロバイダに理解されるロールです。

製作著作 © 2004 Red Hat, Inc.

- 前のページ第13章 Transactions and Concurrency
- 上に戻る
- ホーム
- 次のページ第15章 バッチ処理