JEE Back-end

Mappings bi-directionnels avec JPA

Dans cette séance

Dans la séance précédente, nous avons vu en détail les mappings uni-directionnels.

Dans cette séance, nous allons voir brièvement le cas des mappings bi-directionnels.

Mapping bi-directionnels

Il y a un mapping bi-directionnel si les objets de A contiennent des objets de B ET que les objets de B contiennent des objets de A correspondants.

public class Student {

public class Student {
    private long id;
    private String firstName;
    private String lastName;
    private Set<PhoneNumber> phoneNumbers;
    ...
}

public class PhoneNumber {
    private long id;
    private String phoneNumber;
    private Student owner;
    ...
}        

@OneToMany and @ManyToOne

Dans Student, phoneNumbers est un @OneToMany. Symétriquement, dans PhoneNumber, student est un @ManyToOne.

En BD, cette relation n'est représentée qu'une seule fois. En JPA, un seul des cotés est responsable/propriétaire de la relation. C'est toujours le @ManyToOne. Le côté qui n'est pas responsable doit faire référence au coté responsable avec l'annotation mappedBy.

Pour une @ManyToMany, il faut choisir un coté comme responsable.

Example (1)

@Entity
public class Student {
    @Id
    @GeneratedValue
    private long id;
    private String firstName;
    private String lastName;
    @OneToMany(cascade = CascadeType.ALL,mappedBy="owner")
    private Set<PhoneNumber> phoneNumbers;
}
@Entity
public class PhoneNumber {
    @Id
    @GeneratedValue
    private long id;
    private String phoneNumber;
    @ManyToOne(fetch = FetchType.LAZY)
    private Student owner;
}

Example (2)

  • mappedBy="owner" indique que la relation @OneToMany est en fait un reflet de la relation @ManyToOne sur le champ owner de PhoneNumber.
  • La relation de cascade n'est pas symmétrique. Si l'on supprime un PhoneNumber, on ne veut pas supprimer le Student.
  • On n'oublie pas de changer le FetchType de la relation ManyToOne car il est EAGER par défaut.

Relation bi-directionnelle au niveau des objets

Prenons un Student student et un PhoneNumber phoneNumber.

Si phoneNumber appartient à student.phoneNumbers alors il faut que student==phoneNumber.student.

Il n'y a pas de magie, c'est à votre code de maintenir cette contrainte. JPA ne le fera pas pour vous automatiquement. Si vous enlever un PhoneNumber de student.phoneNumbers, il faut mettre le champ PhoneNumber.student à null.

La norme JPA indique que si la correspondance entre les objets n'est pas maintenue correctement, le mapping vers la BD peut ne pas être correct.

Example

public Student(String firstName, String lastName,Address address,Set<PhoneNumber> phoneNumbers,University university) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.phoneNumbers= new HashSet<>();
        for (PhoneNumber phoneNumber : phoneNumbers) {
            addPhoneNumber(phoneNumber);
        }
        this.address = address;
        this.university = university;
    }

    public void addPhoneNumber(PhoneNumber phoneNumber){
        phoneNumbers.add(phoneNumber);
        phoneNumber.setStudent(this);
    }

    public void removePhoneNumber(PhoneNumber phoneNumber){
        phoneNumbers.remove(phoneNumber);
        phoneNumber.setStudent(null);
    }