L’API JPA et la gestion des entités
JPA (Java Persistence API) L’API de persistance Java JPA est une spécification de Sun. Fondée sur le concept POJO pour la persistance Java, elle est relativement récente puisque sortie en même temps que JEE5, en mai 2006. Disponible depuis les premières versions du JDK 1.5, JPA est une norme, et non une implémentation, chaque fournisseur étant libre d’implémenter son propre framework de persistance tant qu’il respecte les spécifications JPA. JPA permet de mapper les objets POJO avec les tables de la base. Il devient dès lors possible d’utiliser JPA pour stocker les objets Java codés sans avoir à les sous-classer ou à implémenter une interface spécifique, à l’inverse de la lourdeur imposée par EJB 2.x. La persistance traite des aspects de stockage et de récupération des données applicatives. Elle peut maintenant être programmée avec l’API de persistance Java, devenue un standard dans la spécification EJB 3.0 (JSR-220). Elle est apparue en réponse au manque de JEES flexibilité et à la complexité de la spécification J2EE 1.4, en particulier en ce qui concerne la persistance des beans entités. Rappelons que c’est poussé et inspiré par les frameworks Open Source Spring, avec son mécanisme d’injection de dépendances, et Hibernate, et sa gestion de la persistance, qu’une grande partie des mécanismes de persistance EJB3 amenés par JPA ont été construits. Un des grands atouts de l’API JPA est qu’elle est indépendante de tout fournisseur. Elle peut ainsi s’intégrer facilement à des serveurs d’applications JEE ou JSE (Tomcat). JPA est implémentée par deux produits de référence : TopLink, un produit commercial (Oracle) devenu libre, et Hibernate. L’architecture de JPA et son intégration dans l’architecture n-tiers sont illustrées à la figure 10.1. JDBC reste toujours la couche standard utilisée en Java pour l’accès aux données. Les aspects importants de cette nouvelle architecture sont ses relatives stabilité et standardisation. La couche d’accès aux données dialoguant avec les interfaces JPA, les développements gagnent en souplesse, puisqu’il n’est plus nécessaire de changer de modèle O/R ni de couche DAO (pour l’accès aux données) en fonction de l’outil de mapping utilisé. Quel que soit le produit qui implémente l’API, l’interface de la couche JPA reste inchangée.
Caractéristiques de JPA
Avec JPA, toute la complexité qui faisait frémir les développeurs d’applications Java appelés à développer des projets à base d’EJB est évacuée. Ses principaux avantages sont les suivants : • Disparition de la multitude d’interfaces (Home, Remote, Local, etc.). • Possibilité d’utiliser JPA à l’intérieur comme à l’extérieur d’un conteneur JEE. • Transformation des beans entité en simples POJO. • Mapping O/R (objet-relationnel) avec les tables de la base facilitée par les annotations. La figure 10.2 illustre le mécanisme permettant la transformation de toute classe régulière (exemple Article) en table correspondante, en utilisant les annotations. En résumé, JPA fournit les services suivants : • Mécanisme à la Hibernate permettant de définir déclarativement le mapping O/R et de mapper un objet à une ou plusieurs tables de la base de données grâce aux annotations de J2SE 5.0. Les annotations peuvent être utilisées pour définir des objets, des relations, du mapping O/R, de l’injection et de la persistance du contexte. JPA fournit en outre une option pour utiliser les descripteurs XML au lieu des annotations. • API permettant de manipuler les beans entité pour effectuer les opérations CRUD de persistance, récupération et suppression des objets. Le développeur s’affranchit ainsi de toutes les tâches rébarbatives d’écriture du code de persistance des objets métier via JDBC et les requêtes SQL associées.• Langage de requête standard pour la récupération des objets (JP QL, une extension de l’EJB QL d’EJB 2.x. C’est sans doute là un des aspects les plus important de la persistance des données, tant les requêtes SQL mal construites ralentissent la base de données. Cette approche affranchit les applications du langage de requête SQL propriétaire
Les beans entité
Comme indiqué précédemment, avec EJB3 les entités deviennent des objets POJO ordinaires, expurgés de la tuyauterie si complexe d’EJB2. Ils représentent exactement le même concept que les entités de persistance Hibernate. Dans la terminologie JPA, l’entité se réfère à un ensemble de données qui peuvent être stockées sur un support (base de données, fichier) et récupérées sous la forme d’un tout indissociable. Une entité possède des caractéristiques spécifiques, comme la persistance ou l’identité (une entité est une instance forcément unique, identifiée grâce à sa clé) et gère les aspects liés à l’atomicité de la transaction. Une classe entité doit respecter les prérequis suivants : • Être annotée avec les annotations du package javax.persistance.Entity. • Posséder un ou plusieurs constructeurs sans argument de type public ou protected. • Ne pas être déclarée comme final. Aucune méthode ou variable d’instance de persistance ne doit être déclarée final. • Hériter d’une autre entité ou d’une classe non-entité. Une classe non-entité peut également hériter d’une classe entité. Les variables d’instance de persistance doivent être déclarées private ou protected et ne pas être accédées directement par les méthodes de la classe entité. Les clients doivent accéder à l’état de l’entité à travers l’invocation de méthodes accesseur. En résumé, une entité EJB3 est un POJO ordinaire dont le mapping est défini par les annotations du JDK5 ainsi que par les annotations EJB3 et/ou tout framework JEE comme JBoss Seam par exemple. Le mapping peut être de deux types : • logique (associations de la classe, etc.) ; • physique (décrivant le schéma physique, avec les tables, colonnes, index, etc.). Annotations de persistance des beans entité Comme nous l’avons vu, toute classe Java peut facilement être transformée en objet entité grâce aux annotations. Les spécifications EJB3 et JPA apportent un certain nombre d’annotations liées à la persistance des entités, comme le mapping à une table, les colonnes associées à des types de données simple, le mapping de clés primaires, le support de la génération automatique de l’identifiant de la clé ou la gestion des relations entre entités. JEES Livre Page 228 Jeudi, 29. novembre 2007 12:48 12 L’API JPA et la gestion des entités CHAPITRE 10 229 La figure 10.3 illustre les quatre entités principales du domaine webstock de notre étude de cas : Article, Inventaire, Commande et Client. Les cardinalités des relations du modèle sont les suivantes : • One-to-One (relation entre Article et Inventaire) : un article est relatif à une ligne d’un inventaire en magasin. • One-to-One (relation entre Article et Commande) : chaque enregistrement d’inventaire contient un et un seul article. • One-to-Many (relation entre Client et Commande) : spécifie qu’une entité est associée avec une collection d’autres entités. Dans notre exemple, un client peut passer plusieurs commandes (on représente d’ailleurs parfois l’extrémité de la relation, ici Commande, par le caractère « * », qui précise la multiplicité). • Many-to-One (relation entre Commande et Client) : relation bidirectionnelle relativement utilisée pour spécifier que plusieurs commandes sont associées à un client. Les cardinalités et les notions de directionnalité et de propriété de la relation (notion liée à la jointure du type de relation) sont essentielles dans la modélisation et le mapping O/. Nous y reviendrons lors du design de l’étude de cas autour de l’extrait du modèle métier webstock. La marche à suivre pour utiliser une entité au sens JPA du terme est la suivante : 1. Création d’une classe représentant les données d’une ou plusieurs tables, soit un simple JavaBean possédant des setters et des getters, comme ici pour Article : public classe Article { int id ; private String nomArticle ; Développement EJB3 avec Eclipse et Web Tools PARTIE III 230 private String articleCategorieID; private String fournisseurID; private String description; private long poids; //constructeurs, getter et setter, etc … public Article() {} public Article(int id) { this.id = id; } public int getId() { return id; } public void setId(int id) { this.id = id; } … } 2. Ajout des métadonnées pour indiquer que la classe est une entité : import javax.persistence.* ; @Entity ➊ public class Article { int id ; private String nomArticle ; private String articleCategorieID; private String fournisseurID; private String description; private long poids; //constructeurs, getter et setter, etc … public Employe() {} public Employe(int id) { this.id = id; } public int getId() { return id; } public void setId(int id) { this.id = id; } … } 3. Marquage de la classe avec l’annotation @Entity ➊ spécifiant au moteur de persistance que les objets créés avec cette classe peuvent utiliser le support de JPA pour les rendre persistants. JEES Livre Page 230 Jeudi, 29. novembre 2007 12:48 12 L’API JPA et la gestion des entités CHAPITRE 10 231 4. Ajout des métadonnées de mapping O/R sous forme d’annotation (ou de fichier XML au besoin) : import javax.persistence.* ; @Entity(name=?Article?) ➊ @Table (name= “Article”, schema= “webstock”) ➋ public class Article { @Id @GeneratedValue ➌ @Column (name= »ARTICLEID ») ➍ private int articleId; private String nomArticle ; private String articleCategorieID; private String fournisseurID; private String description; private long poids; public Article() {} public Article(int id) { this.id = id; } public int getId() { return id; } public void setId(int id) { this.id = id; } // . . . } L’ajout de simples annotations à la classe Article permet de la rendre persistante. Le tableau 10.1 récapitule l’ensemble des annotations standards appliquées aux classes entités.
Clés primaires composées
Si la clé primaire d’une entité mappe plusieurs colonnes de la table, cette clé primaire est dite composée. EJB3 propose deux annotations pour supporter cette fonctionnalité : @IdClass et @EmbeddedId. L’annotation @IdClass L’entité déclare chaque champ qui constitue la clé composée directement dans la classe de l’entité, annotant chacun de ceux-ci avec @Id et spécifiant la classe de la clé composée qui compose ces champs avec @IdClass, comme dans l’exemple suivant : @Entity @IdClass (ArticlePK.class) public class Article { @Id @GeneratedValue @Column (name= »ARTICLEID ») private int articleId; @Id private String nomArticle ; public Integer getArticleId() { return articleId; } public void setArticleId (Integer articleId) { this.articleId = articleId; } public void setNomArticle (String nomArticle) { this.nomArticle = nomArticle; } public String getNomArticle() { return nomArticle; } // … } L’annotation @IdClass identifie un POJO ordinaire, c’est-à-dire sans annotations. Tous les mappings requis par les champs constituant la clé primaire sont spécifiés dans les champs de l’entité (les champs annotés par @Id dans l’entité doivent correspondre aux champs de la classe clé primaire composite) : public class ArticlePK implements Serializable { private integer articleId; private String nomArticle public void setArticleId (integer articleId) { this.articleid = articleId; } public Integer getArticleId() { return articleId; } public void setNomArticle (String nomArticle) { this.nomArticle=nomArticle; } } L’annotation @EmbeddedId L’entité peut designer un ou plusieurs champs qui la constituent comme constituant de sa clé primaire en utilisant l’annotation @EmbeddedId en combinaison avec @Embeddable. Toute annotation @EmbeddedId doit référencer une classe marquée @Embeddable, comme le montre l’extrait suivant d’une variante de l’entité Article : @Entity public class Article { @EmbeddedId private ArticlePK2 articleId ;