EclipseLink et les champs LAZY
Par Cédric Tabin le dimanche 10.01.2016, 10:00 - Java - Lien permanent
En travaillant sur un projet Java EE 7 (Glassfish + EclipseLink), je suis tombé sur un bug avec les champs étant annotés @Basic(fetch = FetchType.LAZY)
. En gros, suivant l'ordre d'appels entre les getters/setters de l'entité, les champs FetchType.EAGER
sont réinitialisés...
Prenons une entité avec uniquement des annotations basiques:
@Entity @Table(name="person") public class Person implements Serializable { @Id @Column(name = "ID") private Integer id; @Column(name = "LASTNAME") private String lastname; @Column(name = "FIRSTNAME") private String firstname; @Column(name = "VERSION") private Integer version; @Lob @Basic(fetch = FetchType.LAZY) @Column(name = "REMARKS") private String remarks; /* getter & setters */ }
Dans mon bean, je met simplement à jour les différents champs de l'entité:
public class ModifierBean implements ModifierBeanLocal { @Resource private UserTransaction userTransaction; @PersistenceContext private EntityManager entityManager; @Override public String modify(Integer id, String lastname, String firstname, String remarks) { try { StringBuilder result = new StringBuilder(256); userTransaction.begin(); Query query = entityManager.createQuery("SELECT p FROM Person p WHERE p.id = ?1"); query.setParameter(1, id); Person p = (Person)query.getSingleResult(); result.append("State 0: lastname=").append(p.getLastname()).append(", firstname=").append(p.getFirstname()).append(", version=").append(p.getVersion()).append("<br />"); p.setFirstname(firstname); p.setLastname(lastname); p.setVersion(p.getVersion()+1); result.append("State 1: lastname=").append(p.getLastname()).append(", firstname=").append(p.getFirstname()).append(", version=").append(p.getVersion()).append("<br />"); p.setRemarks(remarks); // <= reset the other fields result.append("State 2: lastname=").append(p.getLastname()).append(", firstname=").append(p.getFirstname()).append(", version=").append(p.getVersion()).append("<br />"); userTransaction.commit(); return result.toString(); } catch(Exception e) { //... } } }
Voici l'output obtenu lorsque le morceau de code ci-dessus est exécuté:
State 0: lastname=Smith, firstname=John, version=0 State 1: lastname=John2, firstname=Smith2, version=1 State 2: lastname=Smith, firstname=John, version=0
Entre l'état 0 et 1, tout est normal, les champs sont bien mis à jour. Par contre, dès que l'appel à setRemarks
est effectué, les autres champs sont réinitialisés ! Ce comportement est totalement silencieux, à moins d'activer les logs EclipseLink (et encore, il n'y a pas d'erreur explicite, c'est donc très difficile à explorer).
Pour avoir le comportement voulu, il aurait fallu faire ceci:
Person p = (Person)query.getSingleResult(); p.getRemarks(); //force le chargement du champ LAZY p.setFirstname(firstname); p.setLastname(lastname); p.setVersion(p.getVersion()+1); p.setRemarks(remarks); //ok
Une autre possibilité serait de faire appel à setRemarks
avant les autres setters, mais cela ne fonctionne que s'il n'y a qu'un seul champ LAZY à mettre à jour.
J'ai exposé cela dans un post sur Stackoverflow. Il y a également une question à ce propos sur le forum EclipseLink. Je dois dire que je suis assez surpris que les développeurs ne prennent pas ce ticket au sérieux, car il me paraît tout de même majeur. A noter qu'en utilisant OpenJPA, il n'y avait aucun problème avec cette annotation.
Une seule chose à faire pour améliorer la situation: voter pour le ticket en question !