-->
These old forums are deprecated now and set to read-only. We are waiting for you on our new forums!
More modern, Discourse-based and with GitHub/Google/Twitter authentication built-in.

All times are UTC - 5 hours [ DST ]



Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 3 posts ] 
Author Message
 Post subject: Propriétés de proxy toujours null ! (avec load ou en LAZY)
PostPosted: Fri Apr 30, 2010 2:20 am 
Newbie

Joined: Fri Apr 30, 2010 2:15 am
Posts: 2
Bonjour,

Je travaille sur un SI de pharmacochimie sur des substances naturels qui se base sur les technos Spring/Maven/Hibernate/Wicket. Cependant avec hibernate, j'ai souvent des problèmes de chargement de valeur pour les proxy hibernate.

En général, je charge mes objets avec des "get" (via le HibernateTemplate fourni par Spring), et afin de minimiser les chargements d'objets j'ai des associations en mode "fetch = FetchType.LAZY". Ces objets sont donc chargés en tant que proxy et quand on accède à ses propriétés, parfois toutes les propriétés de l'objet restent à "null" (même la propriété annotée @Id !). Aucune requête SQL n'est donc exécutée par Hibernate pour aller chercher leur valeurs.

Après de moultes tests et de longues recherches sur ce phénomène inexpliqué, j'ai mis en place un simple exemple qui m'a fait me rendre compte que le même phénomène se passait quand je chargais mes objets avec un "load". Toutes les propriétés restent à "null". Voici le code de l'exemple, on ne peut pas faire plus simple :

Code:
Session session = (Session) sf.openSession();

Purification p1 = (Purification) session.load(Purification.class, 18);
LOG.debug("purification p1, id : '" + p1.idPurification + "', reference : '" + p1.ref + "';");
LOG.debug("classe : " + p1.getClass().getSimpleName() + ", estProxy : " + (p1 instanceof HibernateProxy));

Purification p2 = (Purification) session.load(Purification.class, 4445);
LOG.debug("purification p2, id : '" + p2.idPurification + "', reference : '" + p2.ref + "';");
LOG.debug("classe : " + p2.getClass().getSimpleName() + ", estProxy : " + (p2 instanceof HibernateProxy));

session.close();


Et voici le résultat de son exécution :

Code:
[avec "load" (pas de logs SQL !)]
11:57:09.398 DEBUG (SandboxPage.java:<init>:33) purification p1, id : 'null', reference : 'null';
11:57:09.401 DEBUG (SandboxPage.java:<init>:34) classe : Purification_$$_javassist_21, estProxy : true
11:57:09.402 DEBUG (SandboxPage.java:<init>:37) purification p2, id : 'null', reference : 'null';
11:57:09.403 DEBUG (SandboxPage.java:<init>:38) classe : Purification_$$_javassist_21, estProxy : true
14:58:53.215  INFO (org.apache.wicket.protocol.http.RequestLogger) time=119,event=BookmarkablePage[nc.ird.cantharella.web.pages.SandboxPage()],response=BookmarkablePage[nc.ird.cantharella.web.pages.SandboxPage()],sessionid=67FA7E14AEF7A95984A46E8D06A409B9,sessionsize=2284,sessionstart=Fri Apr 30 14:58:53 NCT 2010,requests=2,totaltime=119,activerequests=0,maxmem=66M,total=40M,used=24M


On voit bien que l'objet est chargé en tant que proxy avec javassist, ainsi que les valeurs du proxy rester à "null".
Un autre phénomène bizarre est que lorsque je charge un objet qui n'existe pas (l'id 17 existe dans la base mais pas l'id 4455 !), le comportement est exactement le même. Bien que dans les docs, il soit spécifié qu'une exception devrait être lancer. La javadoc d'HibernateTemplate de Spring" précise notamment :

Quote:
public void load(Object entity,
Serializable id)
throws DataAccessException

Description copied from interface: HibernateOperations
Load the persistent instance with the given identifier into the given object, throwing an exception if not found.


Le même exemple, avec des "get" au lieu des "load" donne :

Code:
[avec get]
... LOGS SQL...
12:00:21.233 DEBUG (SandboxPage.java:<init>:33) purification p1, id : '18', reference : 'RRR';
12:00:21.236 DEBUG (SandboxPage.java:<init>:34) classe : Purification, estProxy : false
... LOGS SQL ...
12:00:21.243 ERROR (org.apache.wicket.RequestCycle) Can't instantiate page using constructor public nc.ird.cantharella.web.pages.SandboxPage()
org.apache.wicket.WicketRuntimeException: Can't instantiate page using constructor public nc.ird.cantharella.web.pages.SandboxPage()
...
Caused by: java.lang.NullPointerException
   at nc.ird.cantharella.web.pages.SandboxPage.<init>(SandboxPage.java:37)


Tout est alors normal : le premier objet est là, bien chargé, et le second n'est effectivement pas trouvé.


Voici pour info, le code de ma classe "Purification". On pourra noter que pour que des fonctions comme getLotSource fonctionne, je suis obligé d'appeler sur certains objets la méthode "HibernateTools.initProxy" afin de remplacer le proxy par l'objet initialisé (j'y fais un "...getHibernateLazyInitializer().getImplementation()").


Code:
@Entity
public class Purification extends AbstractModel implements Comparable<Purification> {

   /** Logger */
   // private static final Log LOG = LogTools.getLog();
   /** Id de la purification */
   @Id
   @GeneratedValue
   public Integer idPurification;

   /** Référence de la manip */
   @Length(max = LENGTH_MEDIUM_TEXT)
   @Column(unique = true)
   @NotEmpty
   public String ref;

   /** Manipulateur */
   @NotNull
   @ManyToOne(fetch = FetchType.EAGER, optional = false)
   public Personne manipulateur;

   /** Date de la manip */
   @NotNull
   @Temporal(TemporalType.DATE)
   public Date date;

   /** Méthode pour la purification **/
   @NotNull
   @ManyToOne(fetch = FetchType.EAGER, optional = false)
   public MethodePurification methode;

   // TODO passer en LAZY, sans provoquer de LAZY exception à la mise à jour d'une puri existante
   /** Paramètres qui caractérisent la méthode pour cette purification */
   @NotNull
   @OneToMany(mappedBy = "id.pk1", fetch = FetchType.EAGER)
   @Cascade( { CascadeType.SAVE_UPDATE, CascadeType.DELETE_ORPHAN })
   @OrderBy
   @Fetch(value = FetchMode.SUBSELECT)
   public List<ParamMethoPuriEffectif> paramsMetho;

   /** Produit utilisé pour la purification **/
   @NotNull
   @ManyToOne(fetch = FetchType.EAGER, optional = false)
   public Produit produit;

   /** Masse avant l'extraction **/
   @NotNull
   @Min(value = 0)
   public Float masseDepart;

   /** Commentaire pour la manip */
   @Lob
   public String complement;

   /** Détermine si la manip doit être confidentielle */
   boolean confidentiel;

   /** Date jusqu'à laquelle la purification est confidentielle */
   @Future
   @Temporal(TemporalType.DATE)
   public Date dateConfidentialite;

   /** Créateur */
   @NotNull
   @ManyToOne(fetch = FetchType.LAZY, optional = false)
   public Personne createur;

   /** Fractions produites par la purification */
   @NotNull
   @OneToMany(mappedBy = "purification", fetch = FetchType.LAZY)
   @Cascade( { CascadeType.SAVE_UPDATE, CascadeType.DELETE_ORPHAN })
   @OrderBy
   public List<Fraction> fractions;

   /**
    * Constructeur
    */
   public Purification() {
      fractions = new ArrayList<Fraction>();
      paramsMetho = new ArrayList<ParamMethoPuriEffectif>();
   }

   /**
    * Remonte au lot dont provient la purification
    * @return Le lot
    */
   public Lot getLotSource() {
      Produit curProd = produit;
      while (curProd.isFraction()) {
         // LOG.debug(curProd + " : " + produit.getClass().getSimpleName());
         Fraction curFraction = (Fraction) curProd;
         // LOG.debug("curFraction : " + curFraction);
         // LOG.debug("curFraction.purification : " + curFraction.purification);
         // LOG.debug("curFraction.purification.produit : " + curFraction.purification.produit);
         curProd = curFraction.purification.produit;
      }
      AssertTools.assertClassOrInterface(curProd, Extrait.class);

      Extrait extrait = (Extrait) curProd;

      // LOG.debug("sans proxy : " + extrait.extraction);
      // TODO comprendre pourquoi on doit explicitement charger le proxy pour récupérer les valeurs, valeurs null
      // sinon
      Extraction extraction = HibernateTools.initProxy(extrait.extraction, Extraction.class);
      // LOG.debug("avec proxy : " + extraction);
      // LOG.debug("extrait.extraction :" + extrait.extraction);

      AssertTools.assertNotNull(extraction);
      AssertTools.assertNotNull(extraction.lot);
      return extraction.lot;
   }

   /** {@inheritDoc} */
   @Override
   public final String toString() {
      return ref;
   }

   /** {@inheritDoc} */
   @Override
   public final int compareTo(Purification purification) {
      return toString().compareTo(purification == null ? null : purification.toString());
   }

}


J'utilise actuellement la version 3.5.1-Final d'Hibernate mais j'ai également essayé avec la 3.3.2.GA. J'ai testé aussi de charger Hibernate avec la librairie cglib au lieu de javaassist (changement du "bytecode.provider") mais rien n'y fait !

Merci d'avance à ceux qui pourront me faire avancer dans cette recherche... et désolé pour la longueur du post.


Top
 Profile  
 
 Post subject: Re: Propriétés de proxy toujours null ! (avec load ou en LAZY)
PostPosted: Fri Apr 30, 2010 5:06 am 
Regular
Regular

Joined: Thu May 07, 2009 5:56 am
Posts: 94
Location: Toulouse, France
salut,
la méthode load() essaie toujours de renvoyer un proxy et l'accès à la base de données dans ce cas ne se produit pas. dans le cas d'un objet non existant en bd, une exception tardive sera levée (lors de l'accès à une des méthodes de l'objet). la méthode get(), elle va toujours lancer une requête en bd (puisque elle ne retourne pas de proxy) et par conséquent quand l'objet demandé n'existe pas une exception est levée immédiatement.

ton problème se produit parce que tu touches directement à l'attribut de l'objet sans passer par les getters. Il faut savoir que le proxy intercepte les méthodes d'accès aux propriétés persistantes pour effectuer le chargement à la demande. d'ailleurs, toucher directement l'attribut d'une classe corrompe le principe de l'encapsulation. Si tu passes directement par l'attribut, le proxy ne peut rien intercepter.

donc, tu devrais rendre tes attributs private ou protected et faire un get pour y avoir accès. (important : pas de méthode final dans une entité quand tu utilises proxy!)

_________________
everything should be made as simple as possible, but not simpler (AE)


Top
 Profile  
 
 Post subject: Re: Propriétés de proxy toujours null ! (avec load ou en LAZY)
PostPosted: Thu May 06, 2010 10:13 pm 
Newbie

Joined: Fri Apr 30, 2010 2:15 am
Posts: 2
Merci beaucoup pour ta réponse rapide. La mienne l'est moins... mais tu avais complétement raison, le problème venait bien du fait que mes propriétés étaient en accès direct. Hibernate semblait très bien fonctionner de cette manière sauf quand il s'agit d'utiliser des classes proxy pour effectuer des chargements tardifs.

Oui effectivement, nos classes ne suivaient pas le bon principe d'encapsulation si connu aujourd'hui. Mais ceci était en effet voulu, nous savions les avantages/inconvénients de ce choix d'implémentation. Tous les architectes ne sont pas tous convaincus de l'emploi systématique de getter/setter mais ceci vient aliment un tout autre débat...

Dans notre cas, le gros inconvénient était la gestion des proxy hibernate. Nous étions convaincus que Hibernate savait le gérer et nous pensions que l'annotation de classe "AccessType" servait à cela. Et non, elle sert seulement à déterminer si les annotations sont déclarées au niveau des propriétés ou des accesseurs. Autre erreur de notre part, seules des librairies rajoutant les notions de programmations par aspect (AOP, AspectJ...) permettrait d'intercepter l'appel à une propriété., et Hibernate n'a aucune dépendance vers l'une d'entre elles !

Nous sommes donc retournés à nos bons vieux getters et setters. Merci encore pour le coup de pouce.


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 3 posts ] 

All times are UTC - 5 hours [ DST ]


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
© Copyright 2014, Red Hat Inc. All rights reserved. JBoss and Hibernate are registered trademarks and servicemarks of Red Hat, Inc.