TD5 Initiation aux EJB3 avec Eclipse Ecriture d’un ejb entity Nous allons prendre ici l’exemple d’une gestion de stock très simplifiée. Nous allons gérer de manière persistante une classe produit, c’est-à-dire que nous allons enregistrer les produits dans une base de données. Pour cela nous allons créer un ejb entité lié à une table de la base de données. 1 Préparation du TD 1.1 MySQL Pour simplifier la manipulation de MySQL, installez Wamp Server (présent dans l’archive) ou EasyPHP. Si vous avez déjà installé soit mySQL soit une distribution comme Wampserver, ne réinstallez rien. Vérifiez que vous avez une base qui s’appelle test_entite et qui ne contient pas de table « produit ». Dans le cas contraire, créez la base mais pas la table. 1.2 Le JDBC La deuxième étape consiste à installer le connecteur JDBC pour MySQL et à créer la datasource vers la base test. Décompressez mysql-connector-java-5.1.19.zip sur le bureau Copiez uniquement le JAR mysql-connector-java-5.1.19-bin.jar dans le répertoire : C:\serveurs\jboss-as7.1.1.Final\modules\com\mysql\main (Créez les répertoires mysql et main). Créez un fichier module.xml dans ce même répertoire avec le contenu suivant : <?xml version="1.0" encoding="UTF-8"?> <module xmlns="urn:jboss:module:1.1" name="com.mysql"> <resources> <resource-root path="mysql-connector-java-5.1.19-bin.jar"/> </resources> <dependencies> <module name="javax.api"/> <module name="javax.transaction.api"/> <module name="javax.servlet.api" optional="true"/> </dependencies> </module> Ouvriez avec Notepad++ le fichier C:\serveurs\jboss-as-7.1.1.Final\standalone\configuration\standalone.xml Rajoutez la balise datasource suivante dans la balise datasources <datasource jndi-name="java:jboss/datasources/testDS" pool-name="testDS" enabled="true" usejava-context="true"> <connection-url>jdbc:mysql://localhost:3306/test_entite</connection-url> <driver>mysql</driver> <security> <user-name>root</user-name> <password></password> </security> </datasource> Rajoutez la balise driver suivante dans la balise drivers <driver name="mysql" module="com.mysql"> <xa-datasource-class>com.mysql.jdbc.Driver</xa-datasource-class> </driver> Vous venez d’installer le JDBC comme un module de JBoss et de créer une datasource qui permet à JBoss d’exploiter votre base test. Au prochain démarrage de JBoss, le driver sera automatiquement chargé. ASI TD 5 – Anne Lapujade 1 2 L’EJB entité 2.1 La classe de l’ejb Dans le package hw du projetEJB déjà existant, ajoutez une classe Produit avec le code suivant : package hw; import java.io.Serializable; import import import import import import javax.persistence.Entity; javax.persistence.GeneratedValue; javax.persistence.GenerationType; javax.persistence.Id; javax.persistence.NamedQueries; javax.persistence.NamedQuery; @Entity @NamedQueries ({ @NamedQuery(name="findAllProducts", query="SELECT p FROM Produit p ORDER BY p.quantiteEnStock"), }) public class Produit implements Serializable { @Id @GeneratedValue (strategy=GenerationType.AUTO) private int id; private String libelle; private int quantiteEnStock; public Produit() {} public Produit(String libelle, int quantiteEnStock) { this.libelle = libelle; this.quantiteEnStock = quantiteEnStock; } public int getId() {return id;} public void setId(int nid){id=nid;} public String getLibelle() {return libelle;} public void setLibelle(String libelle) {this.libelle = libelle;} public int getQuantiteEnStock() {return quantiteEnStock;} public void setQuantiteEnStock(int quantiteEnStock){this.quantiteEnStock = quantiteEnStock;} @Override public String toString() { return "Produit n°"+id+" - "+libelle+" - quantite disponible : "+ quantiteEnStock; } } Seules les annotations @Entity et @Id distinguent cette classe d'une classe non persistante. 2.2 L’ejb façade Dans tous les systèmes d'information, la base de données est complexe et faite de plusieurs tables interconnectées par des clés étrangères. De plus, un processus métier fait couramment appel à plusieurs ejb. Par exemple, pour réserver une chambre pour un client, il faut utiliser les ejb des hôtels et des chambres pour vérifier la réservation puis l'ejb des réservations pour rajouter la réservation mais aussi l'ejb du client pour ajouter ou rechercher le client. La gestion de ces processus complexe doit être isolée des ejb entités qui sont chacun dédiés à la gestion d'une table. Pour cela, une solution est de créer un ejb dit façade qui se charge des processus métiers. Cet ejb ne gère pas de la persistance mais synchronise les appels aux ejb persistants, il s'agit donc d'un ejb session sans état. Les EJB3 entity ne sont pas accessibles directement par le client mais uniquement par un EJB session qui lui peut-être remote et donc accessible de l’extérieur. Toujours dans le package hw du ProjetEJB, créez comme précédemment un EJB session remote sans état. Le code sera le suivant : package hw; import java.util.List; import javax.ejb.Remote; @Remote public interface GestionDeStockRemote { public Produit ajouter(Produit produit); public Produit rechercherProduit(int id); public List<Produit> listerTousLesProduits(); } ASI TD 5 – Anne Lapujade 2 package hw; import java.util.List; import javax.ejb.LocalBean; import javax.ejb.Stateless; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; @Stateless @LocalBean public class GestionDeStock implements GestionDeStockRemote { public GestionDeStock() {} @PersistenceContext EntityManager em; @Override public Produit ajouter(Produit produit) { em.persist(produit); return produit; } @Override public List<Produit> listerTousLesProduits() { return em.createNamedQuery("findAllProducts").getResultList(); } @Override public Produit rechercherProduit(int id) { return em.find(Produit.class, id); } } 3 Spécification de la source de données La spécification EJB3 standardise l'utilisation d'un fichier, nommé persistence.xml, qui permet de préciser des paramètres techniques liés au mapping objet-relationnel, par exemple le nom de la 'DataSource' à utiliser pour se connecter à la base de données. Pour tester notre EJB entity, nous devons définir le fichier persistence.xml et lier la DataSource que nous avons défini en début d’exercice. D'autre part, l'implémentation EJB3 de JBoss, qui se base sur Hibernate, est en mesure de créer automatiquement la structure relationnelle correspondant à notre EJB. Dans le projet EJB/ejbModule, dans le sousrépertoire META-INF créer le fichier persistence.xml Copier le contenu suivant : <?xml version="1.0" encoding="UTF-8"?> <persistence xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd" version="1.0"> <persistence-unit name="contactUnit" transaction-type="JTA"> <jta-data-source>java:jboss/datasources/testDS</jta-data-source> <properties> <property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5InnoDBDialect"/> <property name="hibernate.show_sql" value="true"/> <property name="hibernate.hbm2ddl.auto" value="update"/> </properties> </persistence-unit> </persistence> (Le nom 'IntroEJB3' est purement arbitraire, le nom de la DataSource est celui défini par JBoss, la valeur 'update' de la propriété 'hibernate.hbm2ddl.auto' indique que nous souhaitons qu'Hibernate crée les tables automatiquement et les mette à jour si nécessaire). ASI TD 5 – Anne Lapujade 3 Démarrez le serveur JBoss. Vérifiez que le déploiement s’est bien passé. En fin de lancement du serveur ProjetEJB.jar se déploie. La console indique si l’opération s’est bien déroulée : 20:34:59,981 INFO [org.jboss.as.server.deployment] (MSC service thread 1-16) JBAS015876: Starting deployment of "ProjetEJB.jar" 20:35:00,061 INFO [org.jboss.as.jpa] (MSC service thread 1-9) JBAS011401: Read persistence.xml for testDS 20:35:00,151 INFO [org.jboss.as.ejb3.deployment.processors.EjbJndiBindingsDeploymentUnitProcessor] (MSC service thread 1-6) JNDI bindings for session bean named GestionDeStock in deployment unit deployment "ProjetEJB.jar" are as follows: java:global/ProjetEJB/GestionDeStock!hw.GestionDeStock java:app/ProjetEJB/GestionDeStock!hw.GestionDeStock java:module/GestionDeStock!hw.GestionDeStock java:global/ProjetEJB/GestionDeStock!hw.GestionDeStockRemote java:app/ProjetEJB/GestionDeStock!hw.GestionDeStockRemote java:module/GestionDeStock!hw.GestionDeStockRemote java:jboss/exported/ProjetEJB/GestionDeStock!hw.GestionDeStockRemote 20:35:00,151 INFO [org.jboss.as.ejb3.deployment.processors.EjbJndiBindingsDeploymentUnitProcessor] (MSC service thread 1-6) JNDI bindings for session bean named Hello in deployment unit deployment "ProjetEJB.jar" are as follows: java:global/ProjetEJB/Hello!hw.HelloRemote java:app/ProjetEJB/Hello!hw.HelloRemote java:module/Hello!hw.HelloRemote java:jboss/exported/ProjetEJB/Hello!hw.HelloRemote java:global/ProjetEJB/Hello!hw.Hello java:app/ProjetEJB/Hello!hw.Hello java:module/Hello!hw.Hello 20:35:00,322 INFO [org.jboss.as.jpa] (MSC service thread 1-2) JBAS011402: Starting Persistence Unit Service 'ProjetEJB.jar#testDS' 20:35:00,723 INFO [org.hibernate.annotations.common.Version] (MSC service thread 1-2) HCANN000001: Hibernate Commons Annotations {4.0.1.Final} 20:35:00,743 INFO [org.hibernate.Version] (MSC service thread 1-2) HHH000412: Hibernate Core {4.0.1.Final} 20:35:00,743 INFO [org.hibernate.cfg.Environment] (MSC service thread 1-2) HHH000206: hibernate.properties not found 20:35:00,743 INFO [org.hibernate.cfg.Environment] (MSC service thread 1-2) HHH000021: Bytecode provider name : javassist 20:35:00,803 INFO [org.hibernate.ejb.Ejb3Configuration] (MSC service thread 1-2) HHH000204: Processing PersistenceUnitInfo [ name: testDS ...] 20:35:01,123 INFO [org.hibernate.service.jdbc.connections.internal.ConnectionProviderInitiator] (MSC service thread 1-2) HHH000130: Instantiating explicit connection provider: org.hibernate.ejb.connection.InjectedDataSourceConnectionProvider 20:35:01,563 INFO [org.hibernate.dialect.Dialect] (MSC service thread 1-2) HHH000400: Using dialect: org.hibernate.dialect.MySQL5InnoDBDialect 20:35:01,584 INFO [org.hibernate.engine.transaction.internal.TransactionFactoryInitiator] (MSC service thread 1-2) HHH000268: Transaction strategy: org.hibernate.engine.transaction.internal.jta.CMTTransactionFactory 20:35:01,594 INFO [org.hibernate.hql.internal.ast.ASTQueryTranslatorFactory] (MSC service thread 1-2) HHH000397: Using ASTQueryTranslatorFactory 20:35:01,784 INFO [org.hibernate.validator.util.Version] (MSC service thread 1-2) Hibernate Validator 4.2.0.Final 20:35:02,744 INFO [org.hibernate.tool.hbm2ddl.SchemaUpdate] (MSC service thread 1-2) HHH000228: Running hbm2ddl schema update 20:35:02,744 INFO [org.hibernate.tool.hbm2ddl.SchemaUpdate] (MSC service thread 1-2) HHH000102: Fetching database metadata 20:35:02,765 INFO [org.hibernate.tool.hbm2ddl.SchemaUpdate] (MSC service thread 1-2) HHH000396: Updating schema 20:35:02,765 INFO [java.sql.DatabaseMetaData] (MSC service thread 1-2) HHH000262: Table not found: Produit 20:35:02,765 INFO [java.sql.DatabaseMetaData] (MSC service thread 1-2) HHH000262: Table not found: Produit 20:35:02,775 INFO [java.sql.DatabaseMetaData] (MSC service thread 1-2) HHH000262: Table not found: hibernate_sequence 20:35:02,815 INFO [org.hibernate.tool.hbm2ddl.SchemaUpdate] (MSC service thread 1-2) HHH000232: Schema update complete 20:35:03,305 INFO [org.jboss.as.server] (DeploymentScanner-threads - 2) JBAS018559: Deployed "ProjetEJB.jar" ASI TD 5 – Anne Lapujade 4 Démarrez phpmyadmin. Si tout s’est déroulé normalement, JBoss a créé deux tables : La table produit Hibernate_sequence qui permet à JBoss de gérer l’incrémentation des clés primaires. 4 Création de l’application cliente Dans le package client du ProjetEJBClient ajoutez une classe GestionDeStockClient avec le code source suivant : package client; import hw.GestionDeStock; import hw.Produit; import java.util.Iterator; import java.util.List; import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.NamingException; public class GestionDeStockClient { public static void main(String[] args) { try{ GestionDeStockRemote stock= lookupRemoteStatelessGestionDeStock(); Produit p = stock.ajouter(new Produit("Tomate", 100)); // Petit affichage pour noter que l'objet récupéré à l'issue de l'ajout dispose bien de sa clé primaire auto générée System.out.println(p.getId()+" - "+p.getLibelle()); p=stock.ajouter(new Produit("Pomme de terre", 5680)); p=stock.ajouter(new Produit("Orange", 23)); p=stock.ajouter(new Produit("Carotte", 115)); p=stock.ajouter(new Produit("Muscadet", 48)); List<Produit> produits = stock.listerTousLesProduits(); for (Iterator iter = produits.iterator(); iter.hasNext();) { Produit eachProduit = (Produit) iter.next(); System.out.println(eachProduit); } } p = stock.rechercherProduit(1); System.out.println("Produit recherché : "); System.out.println(p.getId()+" - "+p.getLibelle()); } catch (NamingException e) {e.printStackTrace();} // Connexion au serveur et lookup du bean private static GestionDeStockRemote lookupRemoteStatelessGestionDeStock() throws NamingException { GestionDeStockRemote remote=null; try { Properties jndiProps = new Properties(); jndiProps.setProperty(Context.URL_PKG_PREFIXES, "org.jboss.ejb.client.naming"); InitialContext ctx = new InitialContext(jndiProps); remote = (GestionDeStockRemote) ctx.lookup("ejb:/ProjetEJB/GestionDeStock!hw.GestionDeStockRemote"); } catch (Exception e) { e.printStackTrace(); } return remote; } } Un clic droit sur cette classe puis Run As Java Application et allez vérifier que les données ont bien été rajoutées dans la base. Notez que vous avez deux consoles. La console côté client affiche : ASI TD 5 – Anne Lapujade 5 1 - Tomate Produit n°3 - Orange - quantite disponible : 23 Produit n°5 - Muscadet - quantite disponible : 48 Produit n°1 - Tomate - quantite disponible : 100 Produit n°4 - Carotte - quantite disponible : 115 Produit n°2 - Pomme de terre - quantite disponible : 5680 Produit recherché : 1 - Tomate Si vous cliquez de nouveau sur l’icône de la console, vous ouvrez la console côté serveur. JBoss indique toutes les opérations qui ont été réalisées par l’ORM : 20:44:18,591 INFO update [stdout] (EJB default - 1) Hibernate: select next_val as id_val from hibernate_sequence for 20:44:18,601 INFO next_val=? [stdout] (EJB default - 1) Hibernate: update hibernate_sequence set next_val= ? where 20:44:18,742 INFO values (?, ?, ?) [stdout] (EJB default - 1) Hibernate: insert into Produit (libelle, quantiteEnStock, id) 20:44:18,872 INFO update [stdout] (EJB default - 2) Hibernate: select next_val as id_val from hibernate_sequence for 20:44:18,872 INFO next_val=? [stdout] (EJB default - 2) Hibernate: update hibernate_sequence set next_val= ? where 20:44:18,903 INFO values (?, ?, ?) [stdout] (EJB default - 2) Hibernate: insert into Produit (libelle, quantiteEnStock, id) 20:44:18,913 INFO update [stdout] (EJB default - 3) Hibernate: select next_val as id_val from hibernate_sequence for 20:44:18,913 INFO next_val=? [stdout] (EJB default - 3) Hibernate: update hibernate_sequence set next_val= ? where 20:44:18,923 INFO values (?, ?, ?) [stdout] (EJB default - 3) Hibernate: insert into Produit (libelle, quantiteEnStock, id) 20:44:18,933 INFO update [stdout] (EJB default - 4) Hibernate: select next_val as id_val from hibernate_sequence for 20:44:18,933 INFO next_val=? [stdout] (EJB default - 4) Hibernate: update hibernate_sequence set next_val= ? where 20:44:18,933 INFO values (?, ?, ?) [stdout] (EJB default - 4) Hibernate: insert into Produit (libelle, quantiteEnStock, id) 20:44:18,953 INFO update [stdout] (EJB default - 5) Hibernate: select next_val as id_val from hibernate_sequence for 20:44:18,953 INFO next_val=? [stdout] (EJB default - 5) Hibernate: update hibernate_sequence set next_val= ? where 20:44:18,953 INFO values (?, ?, ?) [stdout] (EJB default - 5) Hibernate: insert into Produit (libelle, quantiteEnStock, id) 20:44:19,053 INFO [stdout] (EJB default - 6) Hibernate: select produit0_.id as id0_, produit0_.libelle as libelle0_, produit0_.quantiteEnStock as quantite3_0_ from Produit produit0_ order by produit0_.quantiteEnStock 20:44:19,113 INFO [stdout] (EJB default - 7) Hibernate: select produit0_.id as id0_0_, produit0_.libelle as libelle0_0_, produit0_.quantiteEnStock as quantite3_0_0_ from Produit produit0_ where produit0_.id=? ASI TD 5 – Anne Lapujade 6