Version PDF
Transcription
Version PDF
Une introduction à la technologie EJB (3/3) : Travailler avec des données 1 Les source de données dans les EJB Conseil : Reprenez votre TPs sur les EJB basé sur OpenEJB . Nous allons l’étendre en ajoutant de nouveaux EJB et de nouveaux tests. 1.1 Injection de DataSource Pour travailler avec une base de données, le serveur d’application à besoin d’utiliser une source de données ( DataSource ). Il en existe une par défaut que nous pouvons utiliser dans les EJB via une injection. Préparez cet EJB package monpkg.services; import import import import import import java.sql.Connection; java.sql.SQLException; java.sql.Statement; javax.annotation.Resource; javax.ejb.Stateless; javax.sql.DataSource; @Stateless public class MessageManager { @Resource private DataSource ds; public void addMessage(String message) throws SQLException { Connection c = ds.getConnection(); Statement st = c.createStatement(); st.execute("CREATE TABLE IF NOT EXISTS MSG(TEXTE VARCHAR(30))"); st.execute("INSERT INTO MSG(TEXTE) VALUES(’" + message + "’)"); } } Et le test suivant : 1 package monpkg.test; import import import import import static org.junit.Assert.assertNotNull; javax.ejb.EJB; javax.ejb.embeddable.EJBContainer; monpkg.services.MessageManager; org.junit.Test; public class TestMessageManager { @EJB MessageManager messageManager; public TestMessageManager() throws Exception { EJBContainer.createEJBContainer().getContext().bind("inject", this); assertNotNull(messageManager); } @Test public void testAddMessage() throws Exception { messageManager.addMessage("Hello"); messageManager.addMessage("Salut"); } } Vérification : à ce stade, vous utilisez la base de données par défaut ( hssql ) en mémoire. 1.2 Configurer sa source de données Pour pouvoir travailler avec notre base de données MySQL nous allons ajouter un fichier de configuration qui est propre à OpenEJB : • Préparez dans votre projet le répertoire conf , • Placez à l’intérieur le fichier openejb.xml suivant : 2 <?xml version = "1.0" encoding="UTF-8"?> <openejb> <Resource id = "MyDataSource" type="DataSource"> JdbcDriver org.hsqldb.jdbcDriver JdbcUrl jdbc:hsqldb:file:data/hsqldb/hsqldb UserName sa Password JtaManaged true </Resource> <Resource id = "MyUnmanagedDataSource" type="DataSource"> JdbcDriver org.hsqldb.jdbcDriver JdbcUrl jdbc:hsqldb:file:data/hsqldb/hsqldb UserName sa Password JtaManaged false </Resource> <Resource id = "myDS" type="DataSource"> JdbcDriver com.mysql.jdbc.Driver JdbcUrl jdbc:mysql://serveur-de-bases-de-donnees-MySQL/votre-base UserName votre-login Password votre-mot-de-passe JtaManaged false </Resource> </openejb> • N’oubliez pas d’ajouter avec Maven le pilote JDBC 1 . La base de données par défaut est maintenant la première de cette liste. Le mécanisme de configuration des sources de données est malheureusement propre à chaque serveur d’applications. Travail à faire : Faites de nouveau fonctionner l’exemple. Un répertoire doit être créer dans votre projet pour stocker la BD. 1.3 Utiliser sa source de données Dans votre EJB, vous pouvez maintenant choisir la source de données que vous allez utiliser : @Resource(name = "myDS") private DataSource ds; Travail à faire : vérifiez que la BD MySQL est bien utilisée. 2 Utiliser JPA dans un serveur d’applications L’utilisation de la technologie JPA est facilitée dans un environement EJB pour deux raisons : • Les transactions sont automatiquement gérées par le conteneur. • La création d’un EntityManager est assurée par le conteneur. Nous devons donc nous concentrer sur l’essentiel : le codage de l’action métier. 1. http://mvnrepository.com/artifact/mysql/mysql-connector-java 3 2.1 Mise en oeuvre Travail à faire : suivez les étapes ci-dessous. 1. Ajoutez avec Maven la version 4.x de Hibernate 2 (Attention : choissisez la version 4.2.21.Final pour JPA 2.0 qui est compatible avec la version que nous utilisons d’OpenEJB). 2. Créez à la racine des fichiers sources Java le fichier META-INF/persistence.xml avec le contenu cidessous. Ce fichier fait explicitement référence à la source de données configurée précédemment. <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="myMySQLBase" transaction-type="JTA"> <provider>org.hibernate.ejb.HibernatePersistence</provider> <non-jta-data-source>myDS</non-jta-data-source> <properties> <property name="hibernate.show_sql" value="true" /> <property name="hibernate.format_sql" value="true" /> <property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5Inno <property name="hibernate.hbm2ddl.auto" value="update" /> </properties> </persistence-unit> </persistence> 3. Préparez l’entité suivante. C’est un compteur identifié par un nom. 2. http://mvnrepository.com/artifact/org.hibernate/hibernate-entitymanager 4 package monpkg.entities; import java.io.Serializable; import import import import javax.persistence.Column; javax.persistence.Entity; javax.persistence.Id; javax.persistence.Table; @Entity() @Table(name = "BB_COUNTER") public class Counter implements Serializable { private static final long serialVersionUID = 1L; @Id() private String name; @Column(name = "value") private Integer value; public Counter() { super(); } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getValue() { return value; } public void setValue(Integer value) { this.value = value; } } 4. Ajoutez à votre serveur un nouvel EJB : 5 package monpkg.services; import import import import import import import import javax.ejb.EJB; javax.ejb.Stateless; javax.ejb.TransactionAttribute; javax.ejb.TransactionAttributeType; javax.ejb.TransactionManagement; javax.ejb.TransactionManagementType; javax.persistence.EntityManager; javax.persistence.PersistenceContext; import monpkg.entities.Counter; @Stateless @TransactionManagement(TransactionManagementType.CONTAINER) public class CounterManager { @PersistenceContext(unitName = "myMySQLBase") EntityManager em; public Counter getCounter(String name) { return em.find(Counter.class, name); } public void removeCounter(String name) { Counter c = em.find(Counter.class, name); if (c != null) { em.remove(c); } } public void createCounter(String name, Integer value) { removeCounter(name); Counter c = new Counter(); c.setName(name); c.setValue(value); em.persist(c); } } L’annotation @TransactionManagement n’est pas vraiment utile car elle indique que les transactions doivent être gérées par le conteneur ce qui est la politique par défaut. Nous aurions pu utiliser l’annotation @TransactionManagement(TransactionManagementType.BEAN) pour indiquer que c’est l’EJB qui va gérer manuellement ses transactions. Ce point ne sera pas détaillé. Vous trouverez plus d’information sur ce tutoriel 3 . 5. Testez votre EJB avec la méthode ci-dessous et vérifiez dans la BD que la ligne est correctement créée. 3. http ://blog.paumard.org/cours/jpa/chap06-ejb-transaction-bean.html 6 @EJB CounterManager cm; @Test public void testCounterManager() { assertNotNull(cm); cm.createCounter("C1", 10); Counter c = cm.getCounter("C1"); assertTrue(10 == c.getValue()); } Vous remarquez que les deux méthodes ( removeCounter et createCounter ) travaillent sur la même transaction qui est validée à la sortie de la méthode createCounter . 2.2 Gestion des erreurs Imaginons que nous ajoutions à notre EJB un test sur la valeur du compteur : public void createCounter(String name, Integer value) throws BadCounter { removeCounter(name); Counter c = new Counter(); c.setName(name); c.setValue(value); em.persist(c); if (value < 0) { throw new BadCounter("pas moins de zéro"); } } L’erreur est codée par une exception métier monpkg.services.BadCounter : package monpkg.services; import javax.ejb.ApplicationException; @ApplicationException(rollback = true) public class BadCounter extends Exception { public BadCounter(String msg) { super(msg); } } Remarque : le test du compteur est volontairement placé à la fin pour générer une éventuelle exception et défaire les modifications déjà effectuées. Travail à faire : Prévoir deux tests unitaires. Le premier pour tester la bonne génération de l’erreur et le second pour vérifier que la valeur précédente d’un compteur n’est pas supprimée en cas d’erreur. Travail à faire : Testez le cas où rollback = false . 2.3 Partage de transaction Nous venons de voir que les méthodes d’un EJB partagent la transaction courante. Mais quelle est la situation entre EJB différents. Imaginons que nous ayons un deuxième EJB spécialisé dans la suppression des compteurs : 7 package monpkg.services; import import import import import import import javax.ejb.Stateless; javax.ejb.TransactionAttribute; javax.ejb.TransactionAttributeType; javax.ejb.TransactionManagement; javax.ejb.TransactionManagementType; javax.persistence.EntityManager; javax.persistence.PersistenceContext; import monpkg.entities.Counter; @Stateless @TransactionManagement(TransactionManagementType.CONTAINER) public class CounterRemover { @PersistenceContext(unitName = "myMySQLBase") EntityManager em; public void removeCounter(String name) { Counter c = em.find(Counter.class, name); if (c != null) { em.remove(c); } } } Nous pouvons revoir la méthode createCounter de l’EJB CounterManager pour utiliser l’EJB CounterRemover : @EJB CounterRemover cr; public void createCounter2(String name, Integer value) throws BadCounter { cr.removeCounter(name); Counter c = new Counter(); c.setName(name); c.setValue(value); em.persist(c); if (value < 0) { throw new BadCounter("pas moins de zéro"); } } Travail à faire : Vérifiez que cette deuxième version fonctionne comme la première. En clair, les transactions sont partagées (pour un client donné) entre EJB. 2.4 Contrôler le partage de transaction Le fonctionnement précédent convient dans la plupart des cas. Nous avons néamoins besoin de controler ce partage. Cela passe par l’annotation TransactionAttribute . Ajoutons une nouvelle méthode à l’EJB CounterRemover . L’annotation @TransactionAttribute indique que le serveur d’applications doit créer une transaction pour cette méthode et la valider à la sortie de la méthode. 8 @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) public void removeCounterAndCommit(String name) { Counter c = em.find(Counter.class, name); if (c != null) { em.remove(c); } } Nous pouvons maintenant faire une troisième version de createCounter toujours dans l’EJB CounterManager : public void removeAndCreateCounter(String name, Integer value) throws BadCounter { cr.removeCounterAndCommit(name); Counter c = new Counter(); c.setName(name); c.setValue(value); em.persist(c); if (value < 0) { throw new BadCounter("pas moins de zéro"); } } Travaux à faire : • Vérifiez par un test unitaire que la version précédente est toujours supprimée (qu’il y ait ou pas une exception). • Vérifiez qu’un client peut directement utiliser la méthode removeCounterAndCommit . • A la place de la clause REQUIRES NEW utilisez la clause MANDATORY . Quel est l’effet ? • Essayez maintenant la version SUPPORTS . Quel est l’effet ? 9