2010-09-02 10 views
4

J'ai une question sur les relations JPA-2.0 (le fournisseur est Hibernate) et leur gestion correspondante en Java. Supposons que j'ai un département et une entité de l'employé:JPA: question sur l'incompatibilité d'impédance dans les relations OneToMany

@Entity 
public class Department { 
    ... 
    @OneToMany(mappedBy = "department") 
    private Set<Employee> employees = new HashSet<Employee>(); 
    ... 
} 

@Entity 
public class Employee { 
    ... 
    @ManyToOne(targetEntity = Department.class) 
    @JoinColumn 
    private Department department; 
    ... 
} 

Maintenant, je sais que je dois gérer les relations Java moi-même, comme dans le test unitaire suivant:

@Transactional 
@Test 
public void testBoth() { 
    Department d = new Department(); 
    Employee e = new Employee(); 
    e.setDepartment(d); 
    d.getEmployees().add(e); 
    em.persist(d); 
    em.persist(e); 
    assertNotNull(em.find(Employee.class, e.getId()).getDepartment()); 
    assertNotNull(em.find(Department.class, d.getId()).getEmployees()); 
} 

Si je laisse de côté soit e.setDepartment(d) ou d.getEmployees().add(e) les assertions échoueront. Jusqu'ici tout va bien. Que faire si je valide la transaction de la base de données entre?

@Test 
public void testBoth() { 
    EntityManager em = emf.createEntityManager(); 
    em.getTransaction().begin(); 
    Department d = new Department(); 
    Employee e = new Employee(); 
    e.setDepartment(d); 
    d.getEmployees().add(e); 
    em.persist(d); 
    em.persist(e); 
    em.getTransaction().commit(); 
    em.close(); 
    em = emf.createEntityManager(); 
    em.getTransaction().begin(); 
    assertNotNull(em.find(Employee.class, e.getId()).getDepartment()); 
    assertNotNull(em.find(Department.class, d.getId()).getEmployees()); 
    em.getTransaction().commit(); 
    em.close(); 
} 

Dois-je encore gérer les deux côtés de la relation? Non, il s'avère que je n'ai pas à le faire. Avec cette modification

e.setDepartment(d); 
//d.getEmployees().add(e); 

les assertions réussissent encore. Cependant, si je ne place que l'autre côté:

//e.setDepartment(d); 
d.getEmployees().add(e); 

les assertions échouent. Pourquoi? Est-ce parce que l'employé est le côté propriétaire de la relation? Puis-je changer ce comportement en annotant différemment? Ou est-ce toujours le côté "One" de "OneToMany" qui détermine quand le champ de clé étrangère dans la base de données est rempli?

+0

Vous créez une nouvelle EM via 'em.close(); em = emf.createEntityManager(); ', ce qui affecte beaucoup les choses. Essayez d'utiliser une transaction sans créer de nouvelle EM. –

Répondre

5

Les relations d'entités dans JPA possèdent des côtés propriétaires et inverses. Les mises à jour de base de données sont déterminées par l'état du propriétaire.Dans votre cas, Employee est un propriétaire en raison de l'attribut mappedBy.

De l'JPA 2.0 specification:

2,9 relations Entité

...

Les relations peuvent être bi-directionnel ou à sens unique. Une relation bidirectionnelle a à la fois un côté propriétaire et un côté inverse (non propriétaire). Une relation unidirectionnelle a seulement un côté propriétaire. Le côté propriétaire d'une relation détermine les mises à jour de la relation dans la base de données, comme décrit dans la section 3.2.4.

Les règles suivantes appliquent aux relations bidirectionnelles:

  • Le côté inverse d'une relation bidirectionnelle doit se référer à son propriétaire d' côté par l'utilisation de l'élément mappedBy de la OneToOne, OneToMany ou ManyToMany annotation . L'élément mappedBy désigne la propriété ou le champ l'entité qui est le propriétaire de la relation .
  • Les nombreux côté un à plusieurs/plusieurs à un relations bidirectionnelle doit être du côté possédante, d'où l'élément mappedBy ne peut pas être spécifié sur l'annotation de ManyToOne.
  • Pour one-to-one bidirectionnelle relations, le côté propriétaire de correspond à la partie qui contient la clé correspondante.
  • Pour les relations plusieurs-à-plusieurs bidirectionnelles chaque côté peut être le côté propriétaire .
+0

Merci, cela répond à ma question. – wallenborn

7

Je ne sais pas ce que votre test tente de démontrer, mais le fait est que vous devez gérer les deux côtés de l'association lorsque l'on travaille avec des associations bidirectionnelles. Ne pas le faire est incorrect. Période.

Mise à jour: Bien que la référence de spécification mentionnée par axtavt soit bien sûr précise, j'insiste, vous devez absolument définir les deux côtés d'une association bidirectionnelle. Ne pas le faire est incorrect et l'association entre vos entités dans le premier contexte de persistance est cassé. Le JPA wiki book met comme ceci:

Comme toutes les relations bi-directionnelles est votre modèle de l'objet et la responsabilité de l'application pour maintenir la relation dans les deux sens. Il n'y a pas de magie dans JPA, si vous ajoutez ou supprimez d'un côté de la collection, vous devez également ajouter ou supprimer de l'autre côté, voir object corruption. Techniquement, la base de données sera mise à jour correctement si vous ajoutez/supprimez seulement du côté propriétaire de la relation, mais alors votre modèle d'objet sera désynchronisé, ce qui peut causer des problèmes.

En d'autres termes, la seule correcte et sûr moyen de gérer votre association bidirectionnelle en Java est de définir les deux côtés du lien. Cela se fait habituellement en utilisant des méthodes de gestion des liens défensifs, comme celui-ci:

@Entity 
public class Department { 
    ... 
    @OneToMany(mappedBy = "department") 
    private Set<Employee> employees = new HashSet<Employee>(); 
    ... 

    public void addToEmployees(Employee employee) { 
     this.employees.add(employee); 
     employee.setDepartment(this); 
    } 
} 

je le répète, ne pas le faire est incorrect. Votre test ne fonctionne que parce que vous frappez la base de données dans un nouveau contexte de persistance (c'est-à-dire une situation très particulière, pas la situation générale) mais le code se romprait dans beaucoup d'autres situations.

+0

Je ne suggérais pas un raccourci pour la gestion de la relation, j'étais juste un peu déconcerté par le comportement observé. Je vais donc m'assurer que le côté Java gère toujours les deux côtés de la relation, merci. – wallenborn

+0

@wallenborn Désolé, j'ai d'une manière ou d'une autre mal compris la question alors. –

2

La raison pour laquelle le deuxième test dans un nouveau contexte de persistance réussit si vous mettez à jour que le côté possédante dans un contexte précédent est que le fournisseur de persistance ne peut évidemment pas savoir que lorsque vous persistant ne pas mettre à jour l'inverse côté aussi. Il se soucie seulement du côté propriétaire pour des raisons de persistance. Toutefois, lorsque vous obtenez des objets persistants d'un fournisseur de persistance, le fournisseur définit correctement les associations bidirectionnelles des deux côtés (il est simplement supposé qu'ils ont également été persistés correctement). Cependant, comme beaucoup d'autres l'ont déjà signalé, le fournisseur de persistance n'a pas la responsabilité de compléter les associations bidirectionnelles nouvellement créées et vous devriez toujours maintenir correctement les associations bidirectionnelles dans votre code.

+0

Oui, la base de données ne peut stocker qu'une seule information, et lorsque l'objet est extrait de la base de données, JPA reconstruit les deux côtés. – wallenborn