Les associations entre entités avec JPA

Contrôles des relations entre entités avec JPA.

Université de Toulon

LIS UMR CNRS 7020

2026-10-01

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
java.io.IOException: Cannot run program "/opt/local/bin/dot": Exec failed, error: 2 (No such file or directory) 
    at java.base/java.lang.ProcessBuilder.start(ProcessBuilder.java:1112)
    at java.base/java.lang.ProcessBuilder.start(ProcessBuilder.java:1046)
    at net.sourceforge.plantuml.dot.ProcessRunner.run(ProcessRunner.java:71)
    at net.sourceforge.plantuml.dot.ProcessRunner.run(ProcessRunner.java:60)
    at net.sourceforge.plantuml.dot.GraphvizVersionFinder.dotVersion(GraphvizVersionFinder.java:120)
    at net.sourceforge.plantuml.dot.GraphvizVersionFinder.getVersion(GraphvizVersionFinder.java:75)
    at net.sourceforge.plantuml.dot.GraphvizRuntimeEnvironment.getVersion(GraphvizRuntimeEnvironment.java:86)
    at net.sourceforge.plantuml.svek.DotStringFactory.getGraphvizVersionInternal(DotStringFactory.java:278)
    at net.sourceforge.plantuml.svek.DotStringFactory.getGraphvizVersion(DotStringFactory.java:267)
    at net.sourceforge.plantuml.svek.GraphvizImageBuilder.printEntityInternal(GraphvizImageBuilder.java:381)
    at net.sourceforge.plantuml.svek.GraphvizImageBuilder.printEntity(GraphvizImageBuilder.java:363)
    at net.sourceforge.plantuml.svek.GraphvizImageBuilder.printEntities(GraphvizImageBuilder.java:355)
    at net.sourceforge.plantuml.svek.GraphvizImageBuilder.buildImage(GraphvizImageBuilder.java:225)
    at net.sourceforge.plantuml.svek.CucaDiagramFileMakerSvek.createFileInternal(CucaDiagramFileMakerSvek.java:104)
    at net.sourceforge.plantuml.svek.CucaDiagramFileMakerSvek.createFile(CucaDiagramFileMakerSvek.java:70)
    at net.atmp.CucaDiagram.exportDiagramInternal(CucaDiagram.java:489)
    at net.sourceforge.plantuml.classdiagram.ClassDiagram.exportDiagramInternal(ClassDiagram.java:85)
    at net.sourceforge.plantuml.UmlDiagram.exportDiagramNow(UmlDiagram.java:119)
    at net.sourceforge.plantuml.AbstractPSystem.exportDiagram(AbstractPSystem.java:216)
    at net.sourceforge.plantuml.SourceStringReader.outputImage(SourceStringReader.java:189)
    at net.sourceforge.plantuml.SourceStringReader.outputImage(SourceStringReader.java:147)
    at io.github.spencerpark.ijava.magics.JavaPlantUMLMagics.plantUML(JavaPlantUMLMagics.java:53)
    at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
    at java.base/java.lang.reflect.Method.invoke(Method.java:565)
    at io.github.spencerpark.jupyter.kernel.magic.registry.Magics.invoke(Magics.java:89)
    at io.github.spencerpark.jupyter.kernel.magic.registry.Magics$CellReflectionMagicFunction.execute(Magics.java:164)
    at io.github.spencerpark.jupyter.kernel.magic.registry.Magics.applyCellMagic(Magics.java:36)
    at REPL.$JShell$61.do_it$($JShell$61.java:65)
    at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
    at java.base/java.lang.reflect.Method.invoke(Method.java:565)
    at io.github.spencerpark.ijava.execution.IJavaExecutionControl.lambda$execute$0(IJavaExecutionControl.java:95)
    at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:328)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1090)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:614)
    at java.base/java.lang.Thread.run(Thread.java:1474)
Caused by: java.io.IOException: Exec failed, error: 2 (No such file or directory) 
    at java.base/java.lang.ProcessImpl.forkAndExec(Native Method)
    at java.base/java.lang.ProcessImpl.<init>(ProcessImpl.java:300)
    at java.base/java.lang.ProcessImpl.start(ProcessImpl.java:231)
    at java.base/java.lang.ProcessBuilder.start(ProcessBuilder.java:1078)
    ... 34 more
Figure 1: Un Customer a une Biography
No JDBC connection available. Set system properties 'jdbc.url' (and optionally 'jdbc.user'/'jdbc.password'), or provide a Connection in the kernel environment.
Figure 2
  • 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

No JDBC connection available. Set system properties 'jdbc.url' (and optionally 'jdbc.user'/'jdbc.password'), or provide a Connection in the kernel environment.
Figure 4

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<>();

}
No JDBC connection available. Set system properties 'jdbc.url' (and optionally 'jdbc.user'/'jdbc.password'), or provide a Connection in the kernel environment.
Figure 6

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)

12:25:16.807 [IJava-executor-0] INFO  notebook -- Order(id=1, date=2026-10-01T12:25:16.666150) [Line(id=1, product=Paper, price=5.0), Line(id=3, product=Scissor, price=8.0), Line(id=2, product=Pen, price=1.0)]
No JDBC connection available. Set system properties 'jdbc.url' (and optionally 'jdbc.user'/'jdbc.password'), or provide a Connection in the kernel environment.
Figure 7
No JDBC connection available. Set system properties 'jdbc.url' (and optionally 'jdbc.user'/'jdbc.password'), or provide a Connection in the kernel environment.
Figure 8

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)

No JDBC connection available. Set system properties 'jdbc.url' (and optionally 'jdbc.user'/'jdbc.password'), or provide a Connection in the kernel environment.
Figure 10

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)

No JDBC connection available. Set system properties 'jdbc.url' (and optionally 'jdbc.user'/'jdbc.password'), or provide a Connection in the kernel environment.
Figure 11
No JDBC connection available. Set system properties 'jdbc.url' (and optionally 'jdbc.user'/'jdbc.password'), or provide a Connection in the kernel environment.
Figure 12
No JDBC connection available. Set system properties 'jdbc.url' (and optionally 'jdbc.user'/'jdbc.password'), or provide a Connection in the kernel environment.
Figure 13