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.