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 :
Et voici le résultat de son exécution :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11 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();
On voit bien que l'objet est chargé en tant que proxy avec javassist, ainsi que les valeurs du proxy rester à "null".
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6 [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
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 :
Le même exemple, avec des "get" au lieu des "load" donne :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.
Tout est alors normal : le premier objet est là, bien chargé, et le second n'est effectivement pas trouvé.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10 [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)
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()").
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 !
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125 @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()); } }
Merci d'avance à ceux qui pourront me faire avancer dans cette recherche... et désolé pour la longueur du post.
Partager