Pour l'exemple, voici le schéma des données utilisées :

OneToMany.png

La table Person et Address ont chacune une clé primaire composée des champs domainpk et idpk. Le premier représente le domaine d'application (c'est n'est pas utile pour la démonstration, les valeurs seront les mêmes) et le deuxième contiendra un identifiant numérique (qui pourrait donc être le même dans un domaine différent).

Pour commencer, voici le code simplifié de la classe Person.java :

@Entity
public class Person implements Serializable
{
    @EmbeddedId
    protected PersonPK personPK;
 
    @Column(name="name")
    private String name;
 
    @Column(name="firstname")
    private String firstName;
}

La clé primaire est stockée dans une classe spéciale PersonPK qui contient les deux champs qui la composent :

@Embeddable
public class PersonPK implements Serializable
{
    @Column(name = "domainpk")
    private String domainpk;
 
    @Column(name = "idpk")
    private int idpk;
}

Evidemment, les classes contiennent également les getters/setters appropriés ainsi que les autres méthodes nécessaires (equals et hashCode).

Pour les champs standards de la table Address, la structure est la même que pour la classe Person.java. Par contre, pour référencer la personne (plutôt que d'avoir les champs domperson et idperson) il est possible d'utiliser l'annotation @ManyToOne pour récupérer directement l'objet en question :

@Entity
public class Address implements Serializable
{
    //...
 
    @ManyToOne
    @JoinColumns({
        @JoinColumn(name="idperson", referencedColumnName="idpk"),
        @JoinColumn(name="domperson", referencedColumnName="domainpk")
    })
    private Person person;
}

A présent, il est donc possible de connaître la personne à qui appartient l'adresse. L'annotation @OneToMany permet de faire l'inverse, à savoir récupérer toutes les adresses d'une personne !

Elle se place donc dans la classe Person.java de la manière suivante :

@Entity
public class Person implements Serializable
{
     //...
 
    @OneToMany(mappedBy="person")
    private Collection<Address> addresses;
}

L'attribut mappedBy indique le nom du champ dans la classe Address.java qui contient l'instance de la classe Person en question.

Encore une dernière subtilité : pour que la liaison soit faite correctement lors de la création d'un objet Address, il faut que les références soient à jour :

//création des objets
Person person = new Person(1, "all", "Tabin", "Cedric");
Address address = new Address(1, "all", "ch. du Rothenberg 5", 1234, "Sion");
 
//mise à jour des références
address.setPerson(person);
person.getAddresses().add(address); //dans l'exemple, l'ajout se fait lors de address.setPerson(...)
 
//persistance du tout
entityManager.persist(person);
entityManager.persist(address);

Et pour récupérer ses données par la suite, rien de plus simple (ou presque) :

Person person = entityManager.find(Person.class, new PersonPK(1, "all"));
Collection<Address> addresses = person.getAddresses();
for (Address address : addresses)
{
    System.out.println(address);
}

J'ai mis en annexe un projet NetBeans avec tout le code et un client web qui fait fonctionner le tout. Avec l'installation par défaut, il faut juste veiller à faire démarrer Java DB (onglet services), puis déployer le projet (F6).

Encore une dernière information : j'ai utilisé la librairie OpenJPA (au lieu de Toplink par défaut) pour gérer la persistance. A noter que la prochaine implémentation de référence pour JPA 2.0 sera EclipseLink !