Salut, Sur mon temps libre, j'ai essayé d'utiliser la méthode merge d'hibernate pour simplifier les problématiques de mises à jour de collection en cascade depuis une composition (ajout/suppression/modification) sans avoir à le faire à la main. La méthode merge[1] est un peu différente de saveOrCreate[2] et permet de pouvoir rattacher les objets correctement et d'éviter les problèmes sur les cascade. En effet, la méthode update pose problème sur les objets modifiés en cascade (déjà rattaché à la session) et n'effectue pas les suppressions. Un post intéressant sur les différences : [3] Le seul souci dans Topia, c'est que les topiaId sont générés sur la méthode create des DAO. Mais vu que dans ce cas les créations se font en cascade, on ne passe pas par la méthode create. Deux solutions : - soit appeler la méthode create sur toute les nouvelles entités (cela reviendrait à parcourir toute la collection à mettre à jour...) - soit utiliser un autre mécanisme pour générer le topiaId. (via un IdentifierGenerator[4] ou un listener sur le persist/merge[5]). Avec hibernate pur, le IdentifierGenerator semble plus approprié. En JPA on peut cependant utiliser le PrePersist pour générer le topiaId. Chose étrange, en hibernate pur, le merge n'envoie pas d'évènement prePersist pour les nouvelles entités, contrairement à la spec JPA. Ci-joint un patch qui implémente la solution du IdentifierGenerator. Cela implique : - modification des mappings générés pour préciser le generator - ajout du generator appliqué au contexte du modèle pour pouvoir récupérer les interfaces des entités simplement avec le TopiaEntityEnum. Le MergeTest illustre les deux cas d'utilisations possibles : - depuis une entité non chargé dans hibernate (newInstance) - depuis une entité chargé dans hibernate (findByTopiaId) Les avantages : - plus besoin de gérer manuellement les merge de collection - la génération du topiaId est intégré aux mécanismes du framework sous-jacent Les inconvénients : - aucun, la génération du topiaId est transparente et ne casse pas la compatibilité avec l'existant Reste à ajouter une méthode merge dans le DAO si cette idée/solution vous semble intéressante à intégrer pour l'évolution de la librairie. Quand pensez-vous ? [1] http://docs.jboss.org/hibernate/core/3.6/javadocs/org/hibernate/Session.html... [2] http://docs.jboss.org/hibernate/core/3.6/javadocs/org/hibernate/Session.html... [3] http://www.stevideter.com/2008/12/07/saveorupdate-versus-merge-in-hibernate/ [4] http://docs.jboss.org/hibernate/core/3.6/javadocs/org/hibernate/id/Identifie... [5] http://www.objectdb.com/java/jpa/persistence/event PS : pour appliquer le patch à la racine du projet topia : patch -p1 -i 0001-Add-merge-test.patch
On Mon, 23 Apr 2012 14:21:08 +0200 Florian Desbois <fdesbois@codelutin.com> wrote:
Le seul souci dans Topia, c'est que les topiaId sont générés sur la méthode create des DAO. Mais vu que dans ce cas les créations se font en cascade, on ne passe pas par la méthode create. Deux solutions : - soit appeler la méthode create sur toute les nouvelles entités (cela reviendrait à parcourir toute la collection à mettre à jour...) - soit utiliser un autre mécanisme pour générer le topiaId. (via un IdentifierGenerator[4] ou un listener sur le persist/merge[5]). Avec hibernate pur, le IdentifierGenerator semble plus approprié. En JPA on peut cependant utiliser le PrePersist pour générer le topiaId. Chose étrange, en hibernate pur, le merge n'envoie pas d'évènement prePersist pour les nouvelles entités, contrairement à la spec JPA.
Je vois une troisième solution. On ajoute dans le constructeur par défaut de TopiaEntityAbstract la creation automatique du topiaId. De cette facon une Entity a toujours un topiaId, une entity sans topiaId n'existe pas. Et l'idée de dire que peut-etre des gens feraient du Topia sans le generateur est surement obsolete. Donc toutes les entites genere heritant de TopiaEntityAbstract auront aussi un topiaId. -- Benjamin POUSSIN -------------------- tél: +33 (0) 2 40 50 29 28 email: poussin@codelutin.com http://www.codelutin.com
On Mon, 23 Apr 2012 18:05:17 +0200 Benjamin POUSSIN <poussin@codelutin.com> wrote:
On Mon, 23 Apr 2012 14:21:08 +0200 Florian Desbois <fdesbois@codelutin.com> wrote:
Le seul souci dans Topia, c'est que les topiaId sont générés sur la méthode create des DAO. Mais vu que dans ce cas les créations se font en cascade, on ne passe pas par la méthode create. Deux solutions : - soit appeler la méthode create sur toute les nouvelles entités (cela reviendrait à parcourir toute la collection à mettre à jour...) - soit utiliser un autre mécanisme pour générer le topiaId. (via un IdentifierGenerator[4] ou un listener sur le persist/merge[5]). Avec hibernate pur, le IdentifierGenerator semble plus approprié. En JPA on peut cependant utiliser le PrePersist pour générer le topiaId. Chose étrange, en hibernate pur, le merge n'envoie pas d'évènement prePersist pour les nouvelles entités, contrairement à la spec JPA.
Je vois une troisième solution. On ajoute dans le constructeur par défaut de TopiaEntityAbstract la creation automatique du topiaId. De cette facon une Entity a toujours un topiaId, une entity sans topiaId n'existe pas.
arch, je ne suis pas convaincu de la solution car l'absence de topiaId est le seul moyen de savoir si une entité n'est pas persistée. Changer ce comportement va avoir pas mal d'effets de bord et va rendre du code non compatible ce qui est à exclure je pense. La bonne solution à mon avis c'est de ne rien changer sur le code existant et d'autoriser la création d'id via JPA si on implante la fonction de merge.
Et l'idée de dire que peut-etre des gens feraient du Topia sans le generateur est surement obsolete. Donc toutes les entites genere heritant de TopiaEntityAbstract auront aussi un topiaId.
Non c'est pas obsolète :( sauf si on trouve un nouveau moyen pour savoir si une entité est persistée. -- Tony Chemit -------------------- tél: +33 (0) 2 40 50 29 28 email: chemit@codelutin.com http://www.codelutin.com
Le 23/04/2012 18:17, Tony Chemit a écrit :
arch, je ne suis pas convaincu de la solution car l'absence de topiaId est le seul moyen de savoir si une entité n'est pas persistée.
Changer ce comportement va avoir pas mal d'effets de bord et va rendre du code non compatible ce qui est à exclure je pense.
+1. Dans Wao, des entity.getTopiaId() == null, il doit y en avoir un sacré paquet. Si le constructeur ajoute un topiaId, ça va mal se mettre. Y'a autre chose qui est louche, lorsque Hibernate remonte des entités de la base, il faut un new EntityImpl(), si le constructeur crée un topiaId, Hibernate le remplacera par celui de l'entité en base via un setter mais l'espace d'un instant, on aura créé un topiaId qui écrase immédiatement. Je préférerait que le constructeur ne fasse strictement rien et qu'il créé vraiment un POJO tout vide. -- Brendan Le Ny, Code Lutin bleny@codelutin.com (+33) 02 40 50 29 28
Le 23/04/2012 14:21, Florian Desbois a écrit :
La méthode merge[1] est un peu différente de saveOrCreate[2] et permet de pouvoir rattacher les objets correctement et d'éviter les problèmes sur les cascade. En effet, la méthode update pose problème sur les objets modifiés en cascade (déjà rattaché à la session) et n'effectue pas les suppressions. Un post intéressant sur les différences : [3]
Aujourd'hui, le fait de remplacer saveOrUpdate() par merge() à l'air de corriger pal mal d'exceptions: Caused by: org.hibernate.NonUniqueObjectException: a different object with the same identifier value was already associated with the session: [org.chorem.lima.entity.FinancialPeriodImpl#org.chorem.lima.entity.FinancialPeriod#1336990931444#0.9703140360262918] at org.hibernate.event.def.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:190) at org.hibernate.event.def.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:143) at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.saveWithGeneratedOrRequestedId(DefaultSaveOrUpdateEventListener.java:210) at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.entityIsTransient(DefaultSaveOrUpdateEventListener.java:195) at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.performSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:117) at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:93) -- Éric Chatellier <chatellier@codelutin.com> Tel: 02.40.50.29.28 http://www.codelutin.com
On Mon, 14 May 2012 12:23:11 +0200 Eric Chatellier <chatellier@codelutin.com> wrote:
Le 23/04/2012 14:21, Florian Desbois a écrit :
La méthode merge[1] est un peu différente de saveOrCreate[2] et permet de pouvoir rattacher les objets correctement et d'éviter les problèmes sur les cascade. En effet, la méthode update pose problème sur les objets modifiés en cascade (déjà rattaché à la session) et n'effectue pas les suppressions. Un post intéressant sur les différences : [3]
Aujourd'hui, le fait de remplacer saveOrUpdate() par merge() à l'air de corriger pal mal d'exceptions: hum, je pense que certains choix ont été fait à l'époque,
il faut retrouver pourquoi et voir quels impacts ça a de changer de code. Je pense pas qu'on puisse décemment dire : ça corrige plus d'exception donc on adopte. Il ne faudrait pas oublier que cela reste pour moi un point très sensible (voire vitale) de bien maîtriser ces problèmatiques et je suis farouchement opposé au fait de changer de tels comportements sans en maîtriser les impacts sur toutes les applications clientes utilisant ToPIA. -- Tony Chemit -------------------- tél: +33 (0) 2 40 50 29 28 email: chemit@codelutin.com http://www.codelutin.com
participants (5)
-
Benjamin POUSSIN -
Brendan Le Ny -
Eric Chatellier -
Florian Desbois -
Tony Chemit