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