mercredi 16 septembre 2009

Nouveautés JPA 2 : Gestion des collections

Vous connaissez certainement JPA, l'api de persistence des objets Java introduite avec Java EE 5.
Avec l'arrivée de la norme JEE 6, JPA dévoile sa version 2.0.
Nous allons voir ensemble les nouveautés apportées par cette nouvelle version.
Pour ceux qui ne sont pas encore familiarisés avec JPA 1er du nom, je vous conseille la lecture de ce tutoriel.

A l'aide de nouvelles annotations, JPA 2 offre de nouvelles manières de mapper et de persister les collections.

@ElementCollection

L'annotation ElementCollection offre des facilités pour la persistence des listes de types primitifs ou de types de base de Java comme Integer ou String.
Auparavant, pour persister une liste de String par exemple, il était nécessaire de persister sous forme de Blob un objet sérialisé contenant les valeurs à enregistrer, cet objet pouvant être une ArrayList ou un HashSet par exemple . Le "probleme" venant du fait que le type String n'étant pas une entité JPA, il ne pouvait être mappé avec un traditionnel @ManyToOne ou @ManyToMany. Un autre contournement possible était de créer une entité JPA comprenant uniquement le String à persister entant qu'attribut et éventuellement une clé technique. Créer une telle table parait logique si on analyse le problème d'un point de vue relationnel, par contre d'un point de vue objet il est dommage d'avoir à s'encombrer d'une telle classe.
Heureusement avec JPA 2 et l'annotation ElementCollection , ce genre de mapping devient beaucoup plus naturel.

Cette annotation s'utilise comme ceci :
@ElementCollection
@CollectionTable(name = "nom")
@Column(name = "value")
List<String> noms;


En utilisant cette annotation, une table comprenant une clé technique + la valeur à enregistrer pour chaque String sera créee automatiquement et utilisée de manière transparente. JPA fait donc bien son travail : s'occuper des problématiques de stockage des données dans le modèle relationnel, en laissant le développeur créer un modèle cohérent d'un point de vue objet.

L'annotation Column permet de spécifier le nom de la colonne qui contiendra les valeurs (des String dans notre exemple) à persister.
Enfin, l'annotation CollectionTable permet de spécifier le nom de la table à générer pour stocker ces valeurs.

Les annotation Column et CollectionTable sont optionnelles, si elles ne sont pas présentes des valeurs par défauts seront utilisées pour les noms de tables et de colonnes.

Et pour les Maps ?

Si l'on désire persister une Map, l'annotation MapKeyColumn permet de spécifier le nom de la colonne qui contient la clé de la Map . On mappe ici des numéros de mois avec le nom du mois , par exemple (3,"Mars") :
@ElementCollection
@CollectionTable(name="track")
@MapKeyColumn (name = "monthNumber")
@Column(name = "month")
private Map<Integer,String> months;

Orphan removal

L'option orphanRemoval a été ajoutée aux annotations @OneToMany et @OneToOne.
Ainsi si un objet anciennement référencé comme lié à votre entité n'est plus attachée à celle ci, il sera supprimé de la base. Ceci évite donc d'avoir des données orphelines qui trainent dans la base. Cela n'est malheureusement pas possible pour les liens de type ManyToMany et ManyToOne car l'ORM ne peut pas savoir après suppression d'un lien si l'ancien objet lié n'est pas référencé par un autre objet sans lancer une requête.

Order column

Enfin, la dernière annotation que nous allons voir aujourd'hui : @OrderColumn permet de spécifier pour une collection d'entités une colonne de tri, pour la lecture et pour l'écriture.
Ainsi au moment de persister la collection, un tri sera effectué pour que les données soient ordonnées en base selon ce critère. Lors de la lecture, un simple "order by" sera utilisé pour ramener les données dans l'ordre souhaité.
Par exemple on trie ici les livres d'une bibliothèque en fonction de leur titre :
@Entity
public class Bibilotheque {
@Id int id;
@ManyToMany
@OrderColumn(name="titre")
List<Livre> livres;
}