Ressource

Dans les cas pratiques suivant, nous souhaitons comprendre l’utilité de la relation bidirectionnelle expliquée dans les sections OneToMany relation et MappedBy. Pour ce faire nous prenons le cas :

  • d’une Commande
  • qui est composée de plusieurs lignes LigneDetail (i.e. un bon de commande est composé de plusieurs lignes représentant chacune des articles)
erDiagram
    COMMANDE {
        LONG id
    }

    LIGNE_DETAIL {
        LONG id
        LONG commande_id FK
    }

    COMMANDE ||--o{ LIGNE_DETAIL : contains
@Entity
public class Commande {
 
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
 
    @OneToMany(mappedBy = "commande", cascade = CascadeType.ALL)
    private List<LigneDetail> ligneDetails = new ArrayList<LigneDetail>();
}
 
@Entity
public class LigneDetail {
 
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
 
 
    @ManyToOne
    private Commande commande;
}

1. Supprimer une ligne d’une commande (NOT WORKING)

Premièrement, regardons ce qu’il se passe si nous ne synchronisons pas les deux côtés de la relation.

@Test
public void testRemoveLigneDetailNotWorking() {
    // Récupérer la commande id=1
    Commande commande = em.find(Commande.class, 1L);
 
    //  Récupérer une ligne associée à la commande (ici la première ligne)
    LigneDetail ligneDetail = commande.getLigneDetails().get(0);
 
    // NOT WORKING      
    em.remove(ligneDetail);
}

2. Non suffisant

@Test
public void testRemoveLigneDetailInsuffisant() {    
    Commande commande = em.find(Commande.class, 1L);
 
    //  Récupérer une ligne associée à la commande (ici la première ligne)
    LigneDetail ligneDetail = commande.getLigneDetails().get(0);
    ligneDetail.setCommande(null) // THIS ADDED
 
    // NOT WORKING      
    em.remove(ligneDetail)
}

3. Supprimer une ligne d’une commande (WORKING)

Dans le code precedent, la ligne était toujours associée à la commande. Nous devons donc supprimer également cette association

@Test
public void testRemoveLigneDetailWorking() {
    Commande commande = em.find(Commande.class, 1L);
    
    LigneDetail ligneDetail = commande.getLigneDetails().get(0);
    commande.getLigneDetails().remove(ligneDetail); // ADD THIS
            
    em.remove(ligneDetail)}

4. Supprimer une ligne d’une commande BIDIRECTIONAL

Mais, ceci n’est pas parfait

By using the bidirectional add sync methods, we can ensure that the persist entity state transition is going to be propagated properly. Without synchronizing both sides of the JPA association, it’s not guaranteed that the entity state will be properly synchronized with the database. source

@Test
public void testRemoveLigneDetailWorkingConventional() {
    Commande commande = em.find(Commande.class, 1L);
    
    LigneDetail ligneDetail = commande.getLigneDetails().get(0);
    commande.getLigneDetails().remove(ligneDetail); // ADD THIS
    ligneDetail.setCommande(null); // ADD THIS
 
    em.remove(ligneDetail);
}

5. Amélioration

Ressource

Comment s’assurer que les développeurs n’oublie pas de synchroniser les deux côté de la relation ?

Dans la classe Commande il est recommandé d’ajouter les méthodes suivante qui viennent simplement remplacer dans nos exemple les deux appels

commande.getLigneDetails().remove(ligneDetail); // ADD THIS
ligneDetail.setCommande(null); // ADD THIS
public class Commande {
	public void addLigneDetails(LigneDetail ligneDetail) {
        ligneDetail.setCommande(this); // this référence la commande
        igneDetails.add(ligneDetail);
	}
 
    public void removeLigneDetails(LigneDetail ligneDetail) {
        ligneDetail.setCommande(null);
        ligneDetails.remove(ligneDetail);
	}
}

Maintenant, plus aucune excuse pour les développeurs ils n’ont qu’à utiliser ces deux méthodes !

@Test
public void improvement() {
    Commande commande = em.find(Commande.class, 1L);
    
    LigneDetail ligneDetail = commande.getLigneDetails().get(0);
    // commande.getLigneDetails().remove(ligneDetail); 
    // ligneDetail.setCommande(null); 
 
    // DEVIENT
    commande.removeLigneDetails(ligneDetail)
 
    em.persist(commande); // optionnelle car commande est un objet MANAGED
}