Viadeo

Antoine Sabot-Durand

Pages

J'ecoute les Cast Codeurs

How to use Hibernate filters in Seam

 english 

JBoss Seam est vraiment un framework très riche mais certaines de ses fonctionalités sont mal documentées.

C’est le cas des filtres Hibernate. La documentation de Seam sur le sujet est plutôt lapidaire et inutilisable en l’état.

Je ne vais pas revenir en détail sur les filtre Hibernate qui sont très bien documentés dans la documentation d’Hibernate, rappelons juste qu’il s’agit d’une solution ingénieuse permettant d’enrichir la clause « where » d’une requête hibernate au runtime.

Pour créer un filtre Hibernate dans le contexte d’un projet Seam, on aura recours aux annotations Hibernate correspondantes.

Prenons comme exemple un stock de véhicule d’occasion pour lequel on a développé toute une série de formulaires de recherche, de pages de navigation et un composant Seam exploitant une collection de requêtes pour peupler les listes de mots clés des formulaires de recherche et orchestrer la navigation dans le stock de véhicules.

Les véhicules comportent un champ « stockVoLib » qui détermine l’origine du véhicule (reprise, véhicule de direction ou véhicule de démonstration). Ce champ était ignoré jusque là et tout à coup notre client souhaite créer une navigation identique à celle existante mais restreinte à un type d’origine (je veux pouvoir naviguer uniquement dans les véhicules de direction).

Pour répondre à cette demande. On peut soit modifier l’existant en enrichissant toutes les pages et toutes les requêtes avec le critère « stockVoLib » soit mettre en œuvre un filtre qui nous fera économiser beaucoup de travail et évitera de fragiliser les processus existant en nous épargnant un refactoring lourd.

Suivant ma règle d’or préférée : « quand on peut déplacer du développement en paramétrage et qu’on ne créé pas une usine à gaz, il ne faut pas hésiter », j’opte pour le filtre et ça tombe bien pour la suite de l’aticle :-) .

1.Création d’un filtre Hibernate en annotations

On commence par définir le filtre soit dans l’entité concernée, soit au niveau package. Comme un filtre peut s’appliquer a plusieurs entités je préfère le définir au niveau package. J’ajoute donc un fichier « package-info.java » dans le package de mes entités pour y définir mon filtre.

@FilterDef(name = "stockLib", defaultCondition = "stockLibele = :aStockLib",
parameters = @ParamDef(name = "aStockLib", type = "string"))

package org.fpp.domain;

import org.hibernate.annotations.FilterDef;
import org.hibernate.annotations.ParamDef;

Mon filtre s’appelle stockLib, il comporte une condition par défaut qui sera utilisée si aucune condition n’est définie à l’application du filtre sur l’entité et il comporte un paramètre de type chaîne appelé « aStockLib ».
A toutes fins utiles, je rappelle que la condition définie dans un filtre correspond à un « where » SQL et non HQL.

J’applique ensuite le filtre sur une ou plusieurs entités de mon application (un filtre peut aussi être appliqué à une collection). En l’occurrence je n’ai qu’une seule entité concernée « usedCar »;

@Entity
@Filter(name = "stockLib")
public class usedCar {
...

2.Utilisation du filtre sous Hibernate

A ce stade, le filtre est utilisable avec Hibernate. On peut l’activer et le paramétrer en récupérant la session Hibernate (si on est en JPA on utilise le getDelegate de l’entityManager).

Session session=(Session) entityManager.getDelegate();
session.enableFilter("stockLib").setParameter("aStockLib", "VD");
...
session.disableFilter("stockLib");

3.Et dans Seam

Mais Seam permet d’aller plus loin en donnant la possibilité de créer un composant filtre (encapsulant le filtre Hibernate) dans lequel on peut injecter dynamiquement les paramètres.
Il suffit d’ajouter ce composant dans le fichiers components.xml de Seam.

<persistence:filter name="stockLibFilter">
  <persistence:name>stockLib</persistence:name>
  <persistence:parameters>
    <key>aStockLib</key>
    <value>#{parameterHdl.stockLib}</value>
  </persistence:parameters>
</persistence:filter>

A noter que la valeur des paramètres dans ce « meta-filtre » Seam doivent toujours être en expression language même si on souhaite injecter une constante : #{‘VD’}.
Ici, « parameterHdl » est un composant Seam dans lequel le paramètre stockLib est injecté depuis l’URL grâce à l’entrée suivante dans le fichier pages.xml

<param name="stockLib" value="#{parameterHdl.stockLib}" required="false"/>

A présent le filtre Seam peut être utilisé pour créer un EntityManager spécifique sur lequel le filtre est activé.

<persistence:managed-persistence-context name="emStockLib" auto-create="true" persistence-unit-jndi-name="java:/fppEntityManagerFactory" >
  <persistence:filters>
    <value>#{stockLibFilter}</value>
  </persistence:filters>
</persistence:managed-persistence-context>

Ce nouvel entityManager pourra être injecté dans les composants à la place l’entityManager standard (sans filtre) pour bénéficier du filtre. A noter que contrairement à Hibernate, le filtre est actif par défaut sur cet entityManger, il n’y a pas de manipulation à faire pour l’activer.
Mais ce n’est pas tout !

4.Activation dynamique des filtres dans Seam

Et c’est là où la documentation de Seam est vraiment lacunaire : l’activation des filtres peut être gérée dynamiquement.

Ainsi on peut écrire quelque chose du type :

<persistence:filter name="stockLibFilter" enabled="#{!(empty parameterHdl.stockLib)}">
  <persistence:name>stockLib</persistence:name>
  <persistence:parameters>
    <key>aStockLib</key>
    <value>#{parameterHdl.stockLib}</value>
  </persistence:parameters>
</persistence:filter>

Notez le paramètre enabled qui contient une expression booléenne. Ici on n’active le filtre que si le champ stockLib du composant parameterHdl n’est pas vide, c’est à dire, par extension, si l’url contient un paramètre stockLib avec une valeur.

Dans ce mode d’utilisation on peut appliquer le filtre à l’entityManager principal de l’application (pas besoin d’en créer un spécifique pour le filtre) et le filtre s’activera ou non en fonction de l’évaluation de son paramètre enabled. Paramètre qui sera évalué à chaque appel de l’entityManager (et oui on est sous Seam).

<persistence:managed-persistence-context name="entityManager" auto-create="true" persistence-unit-jndi-name="java:/fppEntityManagerFactory" >
  <persistence:filters>
    <value>#{stockLibFilter}</value>
  </persistence:filters>
</persistence:managed-persistence-context>

On a donc injecté un filtre Hibernate activé conditionnellement dans l’entityManager et celui-ci s’activera ou non en fonction du context de l’application.
Seule précaution à prendre, si le paramètre enabled du filtre utilise un composant Seam comme c’est le cas ici, il faut que ce composant n’utilise pas l’entityManager dans lequel le filtre est injecté , sinon on obtient un plantage pour cause de référence circulaire au démarrage de l’application.

Share and Enjoy:
  • Print
  • Digg
  • del.icio.us
  • Facebook
  • Google Bookmarks
  • LinkedIn
  • Netvibes
  • Twitter
  • Frank
    thank you very much, it helped a lot.
  • Frank
    thank you very much, it helped a lot.
  • iPatx
    Merci beaucoup pour le tuto mais j'ai un petit souci.
    J'ai l'erreur "Type mismatch : cannot convert from filter to annotation" qui apparaît à la ligne "@Filter"

    Voici la portion de code :


    @FilterDef(name="dateFilter", parameters= @ParamDef( name="dateMin", type="date" ))
    @Entity
    @Filter(name="dateFilter", condition="HdepartPrevu >= :dateMin")
    @Table(name = "transport",catalog = "bdd")
    public class Transport implements java.io.Serializable {


    Voyez vous d'où peut venir mon souci?

    Merci
  • Bonjour et merci,

    A priori votre code devrait fonctionner. Votre erreur ressemble à à un problème de typage. Comment exploitez-vous le filtre au runtime et surtout comment injectez-vous la date ?
  • Prathamesh
    Thank you ! The post was very useful to begin with. However for it didnt worked. Please assist, with what m'I missing? The details are posted at https://forum.hibernate.org/viewtopic.php?f=1&t=997903
  • I answered on the forum here
  • Prathamesh
    Thank you Antoine for prior reply! As suggested I have modified code as:

    package-info.java

    @FilterDef (name = "createdDateWorklogFilter", parameters = {
    @ParamDef (name = "from", type = "java.util.Date"),
    @ParamDef (name = "to", type = "java.util.Date")
    })

    package org.domain.Portal.entity;

    import org.hibernate.annotations.FilterDef;
    import org.hibernate.annotations.ParamDef;


    And used the @Filter on Issues.java and WorkLog.java as:

    @Filter (name = "createdDateWorklogFilter", condition = "CREATED_DATE >= :from and CREATED_DATE <= :to")


    And as trial even swaped the annotation between @Entity and @FilterDef. Still no effect.
  • Two suggestions :

    1) Try to define a "defaultCondition" in your FilterDef.
    2) replace your "java.util.Date" with "date"

    So your filter will be defined like this

    @FilterDef (name = "createdDateWorklogFilter", defaultCondition="CREATED_DATE >= :from and CREATED_DATE < = :to",parameters = {
    @ParamDef (name = "from", type = "date"),
    @ParamDef (name = "to", type = "date")
    })


    If that doesn't work, I'm afraid it goes beyond my knowledge.
  • clyd
    It puzzles me that if I have an entity class with nested list of objects defined with a OneToMany mapping to another table, a filter defined for that nested object class doesnt apply. In other words, when Hibernate eagerly (or lazily) loads those objects when querying the holding object, the filter of the nested objects doesnt seem to work. It should, right?
  • In order to filter a collection within an entity, you have to add the filter to this collection. In other words add the @Filter(name=.....) annotation to the getter of this collection in the containing entity (where you put the @OneToMany annotation)
  • eric: I just encountered the same thing. I believe it's because I had entities in different sub-packages. Instead I added a mapping-file element to my persistence.xml, and pointed the mapping-file at a hibernate configuration file with the filter-def in it.
  • gilad
    Thanks, your post indeed nicely covers an important topic not well documented in the official Seam documentation. Helped me a lot!
  • eric ford
    I cannot get the package level declaration to work. I get a "No such filter configured" error when I call the session.enableFilter method. If I move the @FilterDef into the class then the filtering works as advertised. What am I missing?
  • Hmm. Good question.
  • Thanks for the post, I have been having the same problems.
  • I recently discovered this blog. I've had a similiar idea to your rewriting the ten commandments for some time now.
  • Nice post man i just signed up to flickr to!
  • Excellent essay and site. I put a link to your compassion essay on my website. Good work.
  • Thanks a lot for this post
  • I recently discovered this blog. I've had a similiar idea to your rewriting the ten commandments for some time now.
  • Hmm. Good post.
  • Good post.
  • Hmm. Good.
  • Thanks for the post, I have been having the same problems.
  • Thanks for the post, I have been having the same problems.
  • Thanks for this - great idea.
  • Thanks for the great tips.
  • Hmmm, I am tempted to try this.
  • vraiment intéressant , je vais reprendre du code et simplifier mon appli grâce a ton article. Merci
  • Ingo
    Very well explained.
blog comments powered by Disqus