2011-11-27 6 views
8

J'ai un Parent entité avec un enfant entité dans un ManyToOne relation:Comment éviter les doublons avec les cascades JPA?

@Entity class Parent { 
    // ... 
    @ManyToOne((cascade = {CascadeType.ALL}) 
    private Child child; 
    // ... 
} 

Le enfant a un champ unique:

@Entity class Child { 
    // ... 
    @Column(unique = true) 
    private String name; 
    // ... 
} 

Quand je besoin d'un nouveau Enfant, je demande le ChildDAO fir st:

Child child = childDao.findByName(name); 
if(child == null) { 
    child = new Child(name); 
} 

Parent parent = new Parent(); 
parent.setChild(child); 

Le problème est, si je fais comme ci-dessus deux fois (avec le même nom pour le enfant), et ne persistent que le Parent à la fin, je reçois une exception contrainte. Ce qui semble normal, car il n'y avait initialement aucun enfant dans la base de données avec le nom spécifié. Le problème est, je ne suis pas sûr de ce qui serait le meilleur moyen d'éviter cette situation.

+2

Etes-vous sûr que votre modèle est bon? L'enfant peut avoir plusieurs parents, et le parent ne peut avoir qu'un seul enfant? –

+0

Ma réponse suppose une relation OneToOne - le commentaire ci-dessus est absolument correct, c'est un nom bizarre s'il est correct. –

+0

Oui, le modèle est correct. J'ai renommé les entités pour l'exemple, et il semble que j'aurais pu faire mieux. – Cos64

Répondre

9

Vous créez deux instances non persistantes de Child avec new Child() deux fois, puis en les mettant dans deux parents différents. Lorsque vous persistez les objets Parent, chacune des deux nouvelles instances enfant sera persistée/insérée via cascade, chacune avec un @Id différent. La contrainte unique sur le nom est alors rompue. Si vous faites CascadeType.ALL, chaque fois que vous faites new Child(), vous pouvez obtenir un objet persistant distinct. Si vous voulez vraiment que les deux instances Child soient traitées comme un seul objet persistant ayant le même ID, vous devez le conserver séparément pour l'associer au contexte/à la session de persistance. Les appels suivants à childDao.findByName videront ensuite l'insertion et renverront le nouvel enfant que vous venez de créer. Vous ne procéderez donc pas deux fois à new Child().

1

Vous définissez un objet enfant puis, si persistez le parent, vous enregistrez un registre dans la base de données qui pointe vers un enfant inexistant. Lorsque vous créez un nouvel objet, il doit être géré par entityManager de la même manière lorsque vous "trouvez" un objet à l'aide de DAO, il doit récupérer le registre de DB et placer l'objet dans le contexte entityManager. Essayez d'abord de persister ou de fusionner l'objet enfant.

+0

Le champ enfant du parent a l'ensemble 'CascadeType.ALL'. Aucune fusion explicite n'est requise. –

6

Vous obtenez ceci parce que vous essayez de persister un objet qui existe déjà (même ID). Votre cascade n'est probablement pas persistante, elle est MERGE/UPDATE/REMOVE/DETACH. Le meilleur moyen d'éviter cette situation est de configurer correctement la cascade ou de gérer manuellement les cascades. Fondamentalement parlant, la cascade persiste est le coupable habituel.

Vous voulez probablement quelque chose comme ceci:

//SomeService--I assume you create ID's on persist 
saveChild(Parent parent, Child child) 
{ 
    //Adding manually (using your cascade ALL) 
    if(child.getId() == null) //I don't exist persist 
    persistence.persist(child); 
    else 
    persistence.merge(child); //I'm already in the DB, don't recreate me 

    parent.setChild(child); 
    saveParent(parent); //using a similar "don't duplicate me" approach. 
} 

Les cascades peuvent être extrêmement frustrant si vous laissez le cadre gérer parce que vous manquez de temps en temps des mises à jour si elle est mise en cache (en particulier avec les collections dans ManyToOne des relations). Si vous ne sauvegardez pas explicitement le parent et autorisez le framework à gérer les cascades. En règle générale, j'autorise un Cascade.ALL de mes parents dans une relation et une cascade de tous par DELETE/PERSIST de mes enfants. Je suis un utilisateur Eclipselink donc mon expérience avec Hibernate/other est limitée et je ne peux pas parler de la mise en cache. * Vraiment, pensez-y: si vous enregistrez un enfant, voulez-vous vraiment sauvegarder tous les objets qui s'y rapportent? De plus, si vous ajoutez un enfant, ne devriez-vous pas aviser le parent? *

Dans votre cas, j'aurais simplement une méthode "saveParent" et "saveChild" dans mon Dao/Service qui m'assure que le cache est correct et que la base de données est correcte pour éviter le mal de tête. La gestion manuelle signifie que vous aurez un contrôle absolu et que vous n'aurez pas à compter sur les cascades pour faire votre sale boulot. Dans une direction unique, ils sont fantastiques, dès qu'ils arrivent «en amont», vous allez avoir des comportements inattendus.

3

Si vous créez deux objets et que JPA les conserve automatiquement, vous obtiendrez deux lignes dans la base de données. Donc, vous avez deux options: ne faites pas deux objets, ou ne laissez pas JPA les persister automatiquement.

Pour éviter de créer deux objets, vous devez organiser votre code de sorte que si deux parents tentent de créer des enfants avec le même nom, ils obtiendront la même instance réelle. Vous voudrez probablement un WeakHashMap (limité à la demande actuelle, peut-être en étant référé par des variables locales), avec un nom, où les parents peuvent rechercher le nom de leur nouvel enfant pour voir si l'enfant existe déjà. Sinon, ils peuvent créer l'objet et le placer sur la carte.

Pour éviter que JPA ne conserve les objets automatiquement, supprimez la cascade et utilisez persist pour ajouter manuellement les objets au contexte immédiatement après la création. Comme un contexte de persistance est essentiellement un WeakHashMap trompé attaché à une base de données, ces approches sont assez similaires en ce qui le concerne.

Questions connexes