@OneToMany
ou
@ManyToMany
. Si cette relation n'est pas bidirectionnelle, JPA crée une table de jointure afin de porter cette relation.
Dans l'API Collection, la
Collection
n'a pas de sémantique particulière. Il s'agit juste d'un ensemble d'objets Java sans contrainte, ni d'unicité, ni d'ordonnancement.
Set
a une sémantique particulière : il ne peut pas comporter de doublon. Cette notion impose de définir l'égalité entre objets, ce qui se fait par les méthodes
equals()
et
hashCode()
.
L'implémentation de
Set
qui nous est fournie dans le JDK est
HashSet
. Il s'agit en fait d'une table de hachage, dont les clés sont les codes de hachage des objets ajoutés au
Set
. Si l'on n'a pas respecté le contrat de la classe
Object
, à savoir que deux objets égaux au sens de
equals()
doivent avoir même code de hachage, alors le fonctionnement du
HashSet
ne sera pas celui que l'on attend.
La bonne nouvelle est que la sémantique du
Set
fonctionne en JPA. Si l'on tente d'ajouter un élément à un
Set
qui s'y trouve déjà, cet ajout échouera, et aucune modification ne sera faite en base.
List
est différente de celle du
Set
. On peut ajouter plusieurs fois le même élément dans une liste, sans que cela ne pose de problème. La contrainte est que, lorsque l'on relit les éléments d'une liste, alors on doit obtenir ces éléments dans le même ordre que celui dans lequel on les a enregistrés. En d'autres termes, une liste conserve l'ordre dans lequel les éléments ont été ajoutés.
Ce point est réellement problématique en JPA, et d'ailleurs n'est pas supporté en JPA 1.0.
Lorsque l'on pose une annotation
@OneToMany
sur une relation de type liste, sans autre précision, alors l'ordre des éléments de cette liste sera perdu après une relecture en base.
On peut spécifier un ordonnancement des éléments de deux façons.
Exemple 21. Ordonner les éléments d'une liste par un de leurs champs
// // Entité Bateau // @Entity public class Bateau implements Serializable { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; @OneToMany @OrderBy("name") // doit être un champ de Marin ! private List<Marin> marins ; // suite de la classe } // // Entité Marin // @Entity public class Marin implements Serializable { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; @Column(length=40) private String name ; // suite de la classe }
Bateau
porte une relation
marins
, multivaluée, de type 1:p. Cela signifie qu'un bateau peut posséder plusieurs marins par cette relation, et qu'un marin donné ne peut appartenir qu'à un unique bateau.
Notre champ Java est déclaré comme étant de type
List
, mais la sémantique attachée à ce type n'est pas respectée par JPA. Les marins que l'on enregistre dans cette liste ont donc toutes les chances d'être relus dans un ordre aléatoire.
Pour pallier ce défaut, JPA 1.0 introduit l'attribut
OrderBy
, que nous utilisons ici. Cet attribut doit désigner un champ de l'entité cible de cette relation, ici
Marin
. À chaque lecture de la relation
marins
, JPA ordonnera nos marins en fonction de leur nom, garantissant ainsi un ordre fixe du contenu de la liste. Cet ordre n'est toutefois pas nécessairement le même que celui dans lequel nos marins ont été enregistrés. La sémantique Java n'est donc toujours pas exactement respectée.
L'utilisation de cet attribut n'a pas d'impact sur la structure de base générée. Seules les requêtes SQL sont modifiées s'il est présent.
Si l'on persiste une telle liste, elle ne se triera pas magiquement dans la mémoire de notre application ! D'une façon générale, cette annotation n'impose pas l'ordre des éléments dans la liste
marins
de nos objets
bateau
. Elle ne fait qu'imposer l'ordre de ces éléments lors de la lecture en base.
L'argument de
@OrderBy
peut être une liste de champs de l'entité cible, séparés par des virgules. On peut ajouter le mot-clé
ASC
ou
DESC
après chacun de ces champs, afin de préciser si le classement doit se faire dans l'ordre croissant ou décroissant.
De plus, on prendra garde au fait que cet ordre ne s'applique qu'au moment où l'on lit cette liste de la base. En particulier, si l'on remplit cette liste avec des valeurs rangées dans un ordre qui ne respecte pas l'annotation, JPA ne fait rien pour réordonnancer la liste. Lors de l'itération suivante, même si elle a été mise en base, l'ordre dans lequel les objets seront lus par itération sera celui dans lequel on les a mis.
List
Java. Voyons ceci sur un exemple.
Exemple 22. Ordonner les éléments d'une liste à l'aide du colonne technique
// // Entité Bateau // @Entity public class Bateau implements Serializable { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; @OneToMany @OrderColumn(name="marins_order") private List<Marin> marins ; // suite de la classe } // // Entité Marin // @Entity public class Marin implements Serializable { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; @Column(length=40) private String name ; // suite de la classe }
@OrderColumn
au lieu de l'attribut
orderBy
. Cette annotation ajoute une colonne technique, nommée
marins_order
, utilisée pour enregistrer l'ordre dans lequel nos marins ont été enregistrés.
Dans le cas de la présence d'une table de jointure (cas
@OneToMany
unidirectionnel, ou
@ManyToMany
), cette colonne technique est présente dans la table de jointure, comme on le voit sur la structure que JPA génère pour notre exemple.
Figure 17. Schéma pour une relation multivaluée avec table de jointure ordonnée par une colonne technique
@OneToMany
bidirectionnelle), alors la colonne technique est créée dans la table qui porte la colonne de jointure, c'est-à-dire la table de l'entité maître de la relation.
Figure 18. Schéma pour une relation multivaluée ordonnée par une colonne technique sans table de jointure