hibernate-orm/doc/reference/fr/modules/events.xml

275 lines
12 KiB
XML

<?xml version="1.0" encoding="iso-8859-1"?>
<chapter id="events">
<title>Les intercepteurs et les événements</title>
<para>
Il est souvent utile pour l'application de réagir à certains événements
qui surviennent dans Hibernate. Cela autorise l'implémentation de certaines sortes de
fonctionnalités génériques, et d'extensions de fonctionnalités d'Hibernate.
</para>
<sect1 id="objectstate-interceptors" revision="3">
<title>Intercepteurs</title>
<para>
L'interface <literal>Interceptor</literal> fournit des "callbacks" de la session vers l'application
et permettent à 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'<literal>Interceptor</literal> suivant positionne
<literal>createTimestamp</literal> quand un <literal>Auditable</literal> est créé
et met à jour la propriété <literal>lastUpdateTimestamp</literal> quand un
<literal>Auditable</literal> est mis à jour.
</para>
<para>
Vous pouvez soit implémenter <literal>Interceptor</literal> directement ou (mieux)
étendre <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) {
// ne fait rien
}
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 postFlush(Iterator entities) {
System.out.println("Creations: " + creates + ", Updates: " + updates);
}
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>
Il y a deux types d'intercepteurs: lié à la <literal>Session</literal> et
lié à la <literal>SessionFactory</literal>.
</para>
<para>
Un intercepteur lié à la <literal>Session</literal> est défini
lorsqu'une session est ouverte via l'invocation des méthodes surchargées SessionFactory.openSession()
acceptant un <literal>Interceptor</literal> (comme argument).
</para>
<programlisting><![CDATA[Session session = sf.openSession( new AuditInterceptor() );]]></programlisting>
<para>
Un intercepteur lié a <literal>SessionFactory</literal> est défini avec l'objet <literal>Configuration</literal>
avant la construction de la <literal>SessionFactory</literal>. Dans ce cas, les intercepteurs fournis seront
appliqués à toutes les sessions ouvertes pour cette <literal>SessionFactory</literal>; ceci est vrai
à moins que la session ne soit ouverte en spécifiant l'intercepteur à utiliser.
Les intercepteurs liés à la <literal>SessionFactory</literal> doivent être thread safe, faire attention
à ne pas stocker des états spécifiques de la session puisque plusieurs sessions peuvent utiliser
l'intercepteur de manière concurrente.
</para>
<programlisting><![CDATA[new Configuration().setInterceptor( new AuditInterceptor() );]]></programlisting>
</sect1>
<sect1 id="objectstate-events" revision="4">
<title>Système d'événements</title>
<para>
Si vous devez réagir à des événements particuliers dans votre couche de persistance,
vous pouvez aussi utiliser l'architecture d'<emphasis>événements</emphasis> d'Hibernate3.
Le système d'événements peut être utilisé en supplément ou en remplacement des interceptors.
</para>
<para>
Essentiellement toutes les méthodes de l'interface <literal>Session</literal> sont corrélées à
un événement. Vous avez un <literal>LoadEvent</literal>, un <literal>FlushEvent</literal>, etc
(consultez la DTD du fichier de configuration XML ou le paquet <literal>org.hibernate.event</literal>
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
<literal>Session</literal> 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 <literal>LoadEvent</literal> est traité par
l'implémentation de l'interface <literal>LoadEventListener</literal> déclarée), dans
quel cas leur implémentation devrait être responsable du traitement des
requêtes <literal>load()</literal> faites par la <literal>Session</literal>.
</para>
<para>
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
de variables d'instance.
</para>
<para>
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
<literal>Configuration</literal>, ou spécifiés la configuration XML d'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 :
</para>
<programlisting><![CDATA[public class MyLoadListener implements LoadEventListener {
// C'est une simple méthode définie par l'interface LoadEventListener
public void onLoad(LoadEvent event, LoadEventListener.LoadType loadType)
throws HibernateException {
if ( !MySecurity.isAuthorized( event.getEntityClassName(), event.getEntityId() ) ) {
throw MySecurityException("Unauthorized access");
}
}
}]]></programlisting>
<para>
Vous avez aussi besoin d'une entrée de configuration disant à Hibernate d'utiliser
ce listener en plus du listener par défaut :
</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>
Vous pouvez aussi l'inscrire par programmation :
</para>
<programlisting><![CDATA[Configuration cfg = new Configuration();
LoadEventListener[] stack = { new MyLoadListener(), new DefaultLoadEventListener() };
cfg.EventListeners().setLoadEventListeners(stack);]]></programlisting>
<para>
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 <literal>&lt;listener/&gt;</literal>,
chaque référence sera 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.
</para>
<para>
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. Avoir en plus le type défini durant l'inscription rend plus facile
l'activation ou la désactivation pendant la configuration.
</para>
</sect1>
<sect1 id="objectstate-decl-security" revision="2">
<title>Sécurité déclarative d'Hibernate</title>
<para>
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.
</para>
<para>
D'abord, vous devez configurer les listeners d'événements appropriés pour permettre
l'utilisation d'autorisations 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>
Notez que <literal>&lt;listener type="..." class="..."/&gt;</literal> est juste un raccourci
pour <literal>&lt;event type="..."&gt;&lt;listener class="..."/&gt;&lt;/event&gt;</literal>
quand il y a exactement un listener pour un type d'événement particulier.
</para>
<para>
Ensuite, toujours dans <literal>hibernate.cfg.xml</literal>, lier les permissions aux rôles :
</para>
<programlisting><![CDATA[<grant role="admin" entity-name="User" actions="insert,update,read"/>
<grant role="su" entity-name="User" actions="*"/>]]></programlisting>
<para>
Les noms de rôle sont les rôles compris par votre fournisseur JAAC.
</para>
</sect1>
</chapter>