Hibernate.org Community Documentation

- Précédent
- Suivant

## Chapitre 14. Intercepteurs et événements

Il est souvent utile pour l'application de réagir à certains événements qui surviennent dans Hibernate. Cela autorise l'implémentation de certaines fonctionnalités génériques, et l'extension de fonctionnalités d'Hibernate.

## 14.1. Intercepteurs

L'interface `Interceptor` fournit des "callbacks" de la session vers l'application permettant à l'application de consulter et/ou de manipuler des propriétés d'un objet persistant avant qu'il soit sauvegardé, mis à jour, supprimé ou chargé. Une utilisation possible de cette fonctionnalité est de tracer l'accès à l'information. Par exemple, l'`Interceptor` suivant positionne `createTimestamp` quand un `Auditable` est créé et met à jour la propriété `lastUpdateTimestamp` quand un `Auditable` est mis à jour.

Vous pouvez soit implémenter `Interceptor` directement ou (mieux) étendre `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;

        }

    }

Il y a deux types d'intercepteurs : lié à la `Session` et lié à la `SessionFactory`.

Un intercepteur lié à la `Session` est défini lorsqu'une session est ouverte via l'invocation des méthodes surchargées SessionFactory.openSession() acceptant un `Interceptor` (comme argument).

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

Un intercepteur lié à `SessionFactory` est enregistré avec l'objet `Configuration` avant la construction de la `SessionFactory`. Dans ce cas, les intercepteurs fournis seront appliqués à toutes les sessions ouvertes pour cette `SessionFactory`; ceci est vrai à moins que la session ne soit ouverte en spécifiant l'intercepteur à utiliser. Les intercepteurs liés à la `SessionFactory` doivent être thread safe, en faisant attention à ne pas stocker des états spécifiques de la session puisque plusieurs sessions peuvent utiliser cet intercepteur (potentiellement) de manière concurrente.

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

## 14.2. Système d'événements

Si vous devez réagir à des événements particuliers dans votre couche de persistance, vous pouvez aussi utiliser l'architecture d'événements de Hibernate3. Le système d'événements peut être utilisé en supplément ou en remplacement des interceptors.

Essentiellement toutes les méthodes de l'interface `Session` sont corrélées à un événement. Vous avez un `LoadEvent`, un `FlushEvent`, etc (consultez la DTD du fichier de configuration XML ou le paquetage `org.hibernate.event` pour avoir la liste complète des types d'événement définis). Quand une requête est faite à partir d'une de ces méthodes, la `Session` Hibernate génère un événement approprié et le passe au listener configuré pour ce type. Par défaut, ces listeners implémentent le même traitement dans lequel ces méthodes aboutissent toujours. Cependant, vous êtes libre d'implémenter une version personnalisée d'une de ces interfaces de listener (c'est-à-dire, le `LoadEvent` est traité par l'implémentation de l'interface `LoadEventListener` déclarée), dans quel cas leur implémentation devrait être responsable du traitement des requêtes `load()` faites par la `Session`.

Les listeners devraient effectivement être considérés comme des singletons ; dans le sens où ils sont partagés entre des requêtes, et donc ne devraient pas sauvegarder des états en tant que variables d'instance.

Un listener personnalisé devrait implémenter l'interface appropriée pour l'événement qu'il veut traiter et/ou étendre une des classes de base (ou même l'événement prêt à l'emploi utilisé par Hibernate comme ceux déclarés non-finaux à cette intention). Les listeners personnalisés peuvent être soit inscrits par programmation à travers l'objet `Configuration`, ou spécifiés dans la configuration XML de Hibernate (la configuration déclarative à travers le fichier de propriétés n'est pas supportée). Voici un exemple de listener personnalisé pour l'événement de chargement :

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

            }

        }

    }

Vous avez aussi besoin d'une entrée de configuration indiquant à Hibernate d'utiliser ce listener en plus du listener par défaut :

    <hibernate-configuration>

        <session-factory>

            ...

            <event type="load">

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

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

            </event>

        </session-factory>

    </hibernate-configuration

    >

Vous pouvez aussi l'inscrire par programmation :

    Configuration cfg = new Configuration();

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

    cfg.EventListeners().setLoadEventListeners(stack);

Les listeners inscrits déclarativement ne peuvent pas partager d'instances. Si le même nom de classe est utilisée dans plusieurs éléments `<listener/>`, chaque référence résultera en une instance distincte de cette classe. Si vous avez besoin de la faculté de partager des instances de listener entre plusieurs types de listener, vous devez utiliser l'approche d'inscription par programmation.

Pourquoi implémenter une interface et définir le type spécifique durant la configuration ? Une implémentation de listener pourrait implémenter plusieurs interfaces de listener d'événements. Par ailleurs, le fait de définir le type durant l'inscription, rend l'activation ou la désactivation plus facile au moment de la configuration.

## 14.3. Sécurité déclarative de Hibernate

Généralement, la sécurité déclarative dans les applications Hibernate est gérée dans la couche de session. Maintenant, Hibernate3 permet à certaines actions d'être approuvées via JACC, et autorisées via JAAS. Cette fonctionnalité optionnelle est construite au dessus de l'architecture d'événements.

D'abord, vous devez configurer les listeners d'événements appropriés pour permettre l'utilisation d'autorisations 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"/>

Notez que `<listener type="..." class="..."/>` est juste un raccourci pour `<event type="..."><listener class="..."/></event>` quand il y a exactement un listener pour un type d'événement particulier.

Ensuite, toujours dans `hibernate.cfg.xml`, liez les permissions aux rôles :

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

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

Les noms de rôle sont les rôles compris par votre fournisseur JAAC.

Copyright © 2004 Red Hat, Inc.

- PrécédentChapitre 13. Transactions et Accès concurrents
- Niveau supérieur
- Sommaire
- SuivantChapitre 15. Traitement par lot