Les associations entre entités avec JPA

Université de Toulon

LIS UMR CNRS 7020

2025-01-30

Source
Branch
  • develop (c88a48e)
  • 2024/03/08 11:41:10
Java
  • OpenJDK Temurin-21.0.5+11
  • Apache Maven 3.9.9
Docker
  • Client: 27.3.1 - buildx v0.18.0 - compose v2.30.3
  • Server: 27.3.1

Types de relations

  • Prise en charge complète: gestion des relations entre entités de différents types.
  • Types de relations
    • One-to-One (Un-vers-Un)
    • One-to-Many (Un-vers-Plusieurs)
    • Many-to-Many (Plusieurs-vers-Plusieurs)
  • Unidirectionnelle ou bidirectionnelle.

One To One (1-1)

  • 1-1 en Java mais une seule relation
Figure 1: Un Customer a une Biography
Figure 2: L’unique relation CUSTOMER
  • Pour cela, JPA propose :
    • @Embeddable: Indique que les membres d’une classe peuvent être persistés en tant qu’attributs de la classe qui les utilise.
    • @Embedded: Utilisé avec @Embeddable pour intégrer ses propriétés dans une autre entité

Un client a une et une seule biographie

@Builder
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor

@Embeddable
public class Biography {
    @Column(name = "BIO_BRIEF")
    private String brief;

    @Lob
    @Column(name = "BIO_EXTENDED")
    private String extended;
}
Figure 3: Embeddable.java
@Getter
@Setter
@ToString
@RequiredArgsConstructor(staticName = "of")
@NoArgsConstructor(access = AccessLevel.PROTECTED)

@Entity
@Table(name = "CUSTOMER", schema = "ex_biography")
public class Customer {
    @Id
    @GeneratedValue
    private long id;

    @Column(length = 50, nullable = false)
    @NonNull
    private String name;

    @Embedded
    @Column()
    private Biography biography;
}

Customer.java

Persistence de l’association

Creation et persistence d’un Customer
import fr.univtln.bruno.demos.jpa.hello.samples.ex_biography.Customer;
try (EntityManager entityManager = emf.createEntityManager()) {
   entityManager.getTransaction().begin();
            
   Customer customer = Customer.of("Jim");
   customer.setBiography(Biography.builder()
      .brief("bla")
      .extended("bla bla")
      .build());
   entityManager.persist(customer);
            
   entityManager.getTransaction().commit();
}

dans une seule relation

id name bio_brief bio_extended
1 Jim bla 70666
Figure 4: La table CUSTOMER

One to Many (1-N)

Figure 5: Une Order a plusieurs Line
@Getter
@Setter
@ToString
@AllArgsConstructor(staticName = "of")
@NoArgsConstructor(access = AccessLevel.PROTECTED)

@Embeddable
@Table(name = "LINE", schema = "EX_ONE_TO_MANY_B")
public class Line {
    @NonNull
    private String product;

    private double price;
}
@Getter
@Setter
@ToString
@NoArgsConstructor
@Builder
@AllArgsConstructor

@Entity
@Table(name = "EORDER", schema = "EX_ONE_TO_MANY_B")
public class Order {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    private long id;

    @Builder.Default
    @Column(nullable = false)
    private LocalDateTime date = LocalDateTime.now();

    @ElementCollection
    @CollectionTable(name = "LINE", schema = "EX_ONE_TO_MANY_B")
    @Singular
    private Set<Line> lines = new HashSet<>();

}
Figure 6: Les relations LINE et EORDER

Avec deux entités

  • Dans le cas général :
    • L’attribut de type Collection<T> est annoté avec @OneToMany.
    • Pour une relation bidirectionnelle, l’autre entité (de type T) est annotée avec @ManyToOne.
    • L’attribut optionnel mappedBy indique le nom de l’attribut correspondant dans le type T si l’on souhaite une relation bidirectionnelle.

1 to N à deux entités (1/3)

@Getter
@Setter
@ToString
@NoArgsConstructor

@Entity
@Table(name = "EORDER", schema = "EX_ONE_TO_MANY_A")
public class Order {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    private long id;

    private LocalDateTime date = LocalDateTime.now();

    @OneToMany(mappedBy = "order",
            cascade = {CascadeType.PERSIST,
                    CascadeType.REMOVE},
            orphanRemoval = true)
    @ToString.Exclude
    private Set<Line> lines = new HashSet<>();

    public Order addLine(Line line) {
        line.setOrder(this);
        lines.add(line);
        return this;
    }

    public Order removeLine(Line line) {
        line.setOrder(null);
        lines.remove(line);
        return this;
    }
}

1 to N à deux entités (2/3)

@Getter
@Setter
@ToString
@RequiredArgsConstructor(staticName = "of")
@NoArgsConstructor(access = AccessLevel.PROTECTED)

@Entity
@Table(name = "LINE", schema = "EX_ONE_TO_MANY_A")
public class Line {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    private long id;
    @NonNull
    private String product;

    @NonNull // just for requiredargsconstructor
    private double price;

    @ManyToOne
    @JoinColumn(nullable = false)
    @ToString.Exclude
    private Order order;

    public void setOrder(Order order) {
        if (this.order != null) {
            this.order.getLines().remove(this);
        }
        this.order = order;
        if (order != null) {
            order.getLines().add(this);
        }
    }
}

1 to N à deux entités (3/3)

Figure 7: Les relations LINE et EORDER

order_id order_date line_id product price
1 2025-01-30T18:10:25.293Z 1 Scissor 8
1 2025-01-30T18:10:25.293Z 2 Pen 1
1 2025-01-30T18:10:25.293Z 3 Paper 5
2 2025-01-30T18:10:25.302Z 4 Moto 8000
2 2025-01-30T18:10:25.302Z 5 Car 20000
Figure 8: SELECT * FROM EORDER INNER JOIN LINE

Many to Many (N-M)

  • @ManyToMany en Java Persistence API (JPA) :
    • Utilisée pour modéliser une relation many-to-many entre deux entités.
    • La table d’association est générée et utilisée implicitement.
Figure 9: Un Customer a plusieurs adresses (qui peuvent être associées à d’autres)
Figure 10: Les relation CUSTOMER, ADDRESS et la relation de d’association

Many to Many (1/2)

@Getter
@Setter
@ToString
@RequiredArgsConstructor(staticName = "of")
@NoArgsConstructor(access = AccessLevel.PROTECTED)

@Entity
@Table(name = "CUSTOMER", schema = "EX_MANY_TO_MANY")
public class Customer {
    @Id
    @GeneratedValue
    private long id;

    @NonNull
    private String name;

    @ManyToMany
    @ToString.Exclude
    @JoinTable(schema = "EX_MANY_TO_MANY")
    private Set<Address> places = new HashSet<>();

    public void addPlace(Address address) {
        this.places.add(address);
        address.getOccupants().add(this);
    }

    public void removePlace(Address address) {
        this.places.remove(address);
        address.getOccupants().remove(this);
    }
}
@Getter
@Setter
@ToString
@RequiredArgsConstructor(staticName = "of")
@NoArgsConstructor(access = AccessLevel.PROTECTED)

@Entity
@Table(name = "ADDRESS", schema = "EX_MANY_TO_MANY")
public class Address {
    @Id
    @GeneratedValue
    private long id;

    @NonNull
    private String addressDetail;

    @ManyToMany(mappedBy = "places")
    @ToString.Exclude
    private Set<Customer> occupants= new HashSet<>();

    public void addOccupant(Customer customer) {
        this.occupants.add(customer);
        customer.getPlaces().add(this);
    }

    public void removeOccupant(Customer customer) {
        this.occupants.remove(customer);
        customer.getPlaces().remove(this);
    }

}

Many to Many (2/2)

id name
2 Miss Hans Hagenes
3 Quinn Keeling DVM
4 Reva Welch
5 Kathy Hackett
6 Maxine Vandervort
Figure 11: La table CUSTOMER
occupants_id places_id
2 1
3 1
5 1
6 1
2 2
4 2
5 2
Figure 12: La table de jointure
id addressdetail
1 Suite 304 71024 Deckow Park, Lehnermouth, AK 76082
2 Apt. 432 8182 Linwood Parkways, Tovaville, HI 56620
Figure 13: La table ADDRESS