TP 12 - Persistence avec JPA

publicité
TP 12 - Persistence avec JPA
Pascal GRAFFION
2014/01/12 10:28
GLG203 - TP 12 - Persistence avec JPA
Table des matières
TP 12 - Persistence avec JPA .................................................................................................................................................... 3
Hello PetStore ! ....................................................................................................................................................................... 3
Entity manager ...................................................................................................................................................................... 3
Expression des besoins ............................................................................................................................................................... 5
Analyse et conception ................................................................................................................................................................ 5
Vue logique ............................................................................................................................................................................. 5
Vue processus .......................................................................................................................................................................... 5
Vue implémentation ................................................................................................................................................................ 5
Architecture .......................................................................................................................................................................... 5
Vue déploiement ...................................................................................................................................................................... 6
Implémentation ........................................................................................................................................................................... 7
Classes métiers ........................................................................................................................................................................ 7
DAOs ....................................................................................................................................................................................... 7
AbstractDataAccessObject la superclasse de tous nos DAOs ................................................................................................ 8
Injection des DAO dans nos EJB ............................................................................................................................................ 9
Recette utilisateur ..................................................................................................................................................................... 10
Tests des DAO ......................................................................................................................................................................... 11
Recette utilisateur finale ........................................................................................................................................................ 12
Résumé ..................................................................................................................................................................................... 12
Références ................................................................................................................................................................................ 12
Page 2 - dernière modification par Pascal GRAFFION le 2014/01/12 10:28
GLG203 - TP 12 - Persistence avec JPA
TP 12 - Persistence avec JPA
La persistance des données en EJB 3 a été complètement réarchitecturée au travers de JPA (Java Persistence API). Alors que
nous parlions de composants persistants en EJB 2.x, JPA se recentre sur de simples classes
Java. En EJB 2.x la persistance ne pouvait être assurée qu'à l'intérieur du conteneur alors qu'avec JPA elle peut être utilisée dans
une simple application JSE (Java Standard Edition).
Hello PetStore !
Dans le modèle de persistance JPA, un entity bean est une classe java simple (un Pojo) complétée par de simples annotations :
@Entity // 1
public class Book {
@Id // 2
private Long id;
@Column(nullable = false) // 3
private String title;
private Float price;
@Column(length = 2000) // 3
private String description;
private String isbn;
// ...
Notez la présence d'annotations à plusieurs endroits dans la classe Book :
1. tout d'abord, l'annotation @javax.persistence.Entity permet à JPA de reconnaître cette classe comme une classe
persistante et non comme une simple classe Java.
2. L'annotation @javax.persistence.Id, quant à elle, définit l'identifiant unique de l'objet. Elle donne à l'entity bean une
identité en mémoire en tant qu'objet, et en base de données via une clé primaire. Les autres attributs (description,
isbn, ...) seront rendus persistants par JPA en appliquant les paramétrages par défaut : le nom de la colonne est identique
à celui de l'attribut et le type String est converti en varchar(255).
3. L'annotation @javax.persistence.Column permet de préciser des informations sur une colonne de la table : changer son
nom (qui par défaut porte le même nom que l’attribut), préciser son type, sa taille et si la colonne autorise ou non la
valeur null.
Entity manager
Quand on veut rendre persistent en base de données un entity bean (ou une entité), il faut utiliser un entity manager. Il est
logique d'encapsuler cet entity manager dans un DAO (Data Access Object) :
public class BookDAO {
private EntityManager em;
private EntityTransaction tx;
public BookDAO() {
// Gets an entity manager and a transaction
EntityManagerFactory emf = Persistence.createEntityManagerFactory("petstorePU");
em = emf.createEntityManager();
tx = em.getTransaction(); }
public void persist(Book book) {
tx.begin();
em.persist(book); // 1
tx.commit();
}
Book findByISBN(String isbn) {
String queryString = "select b from Book b where b.isbn = :isbn";
Query query = em.createQuery( queryString ); // 2
query.setParameter( "isbn", isbn );
Book b = null;
b = (Book)query.getSingleResult();
return b;
}
}
L'entity manager peut notamment rendre persistant une entité par sa méthode persist() (1) ou permettre de retrouver une
entité en base en créant une requête JPQL (2)
Page 3 - dernière modification par Pascal GRAFFION le 2014/01/12 10:28
GLG203 - TP 12 - Persistence avec JPA
Contexte de persistance
L'entity manager utilise un contexte de persistance (petstorePU) qui le renseigne sur le type de la base de données et les
paramètres de connexion à cette base de données. Ces informations sont décrites dans le fichier persistence.xml :
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="1.0">
<persistence-unit name="petstorePU" transaction-type="RESOURCE_LOCAL">
<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
<class>domain.Book</class>
<properties>
<property name="eclipselink.target-database" value="MYSQL"/>
<property name="eclipselink.ddl-generation" value="drop-and-create-tables"/>
<property name="eclipselink.logging.level" value="INFO"/>
<property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
<property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/petstorejpadb"/>
<property name="javax.persistence.jdbc.user" value="root"/>
<property name="javax.persistence.jdbc.password" value=""/>
</properties>
</persistence-unit>
</persistence>
Exemple d'utilisation
Le programme ci dessous crée une instance de Book, la rend persistente puis vérifie sa présence dans la base de données :
public class Main {
public static void main(String[] args) {
// Creates an instance of book
Book book = new Book();
String isbn = "1-84023-742-2";
book.setTitle("The Hitchhiker's Guide to the Galaxy");
book.setPrice(12.5F);
// Persists the book to the database
BookDAO dao = new BookDAO();
dao.persist(book);
// Retrieve one book from the database
book = dao.findByISBN(isbn);
System.out.println("Book with isbn = " + isbn);
System.out.println(book);
Pour compiler ce programme utiliser la cible compile, pour l'exécuter utiliser la cible run. Ces cibles sont définies dans le
fichier build.xml fourni; elles requièrent la présence des 2 fichiers jar javax.persistence-2.0.0.jar et eclipselink-2.0.0.jar livrés
dans le répertoire lib.
Le fichier persistence.xml doit être trouvé lors de l'exécution; il doit être présent dans un répertoire META-INF trouvé dans le
classpath.
Ceci est assuré par la cible prepare :
<target name="prepare" depends="check">
<echo message="Setup the Yaps environment"/>
<mkdir dir="${classes.dir}"/>
<mkdir dir="${classes.dir}/META-INF"/>
<copy file="${src.dir}/META-INF/persistence.xml" todir="${classes.dir}/META-INF"/>
<mkdir dir="${build.dir}"/>
</target>
Exécution depuis Eclipse
En lançant l'exécution de ce programme depuis Eclipse, l'erreur suivante risque de s'afficher :
Exception in thread "main" javax.persistence.PersistenceException: No Persistence provider for EntityManager named
petstorePU
Pour contourner cette erreur il faudra copier le fichier Hello/src/META-INF/persistence.xml par exemple dans bin/METAINF/persistence.xml (en supposant que bin est le "default output folder" du projet pour Eclipse (soit le répertoire dans lequel
Eclipse sauvegarde les classes compilées du projet)).
Page 4 - dernière modification par Pascal GRAFFION le 2014/01/12 10:28
GLG203 - TP 12 - Persistence avec JPA
Expression des besoins
Afin de permettre des évolutions moins couteuses de la structure de la base de données et des classes DAO associées, il a été
décidé d'utiliser JPA.
Cette nouvelle évolution est purement technique et non fonctionnelle. Il n'y a pas de nouveaux cas d'utilisation.
Analyse et conception
Vue logique
Nous allons conserver notre architecture en continuant à utiliser des DAOs.
Figure 1 - Diagramme de classe représentant les liens entre les objets du domaine et leurs DAO
Vue processus
Vue implémentation
Identique à l'étape précédente.
Architecture
Dans le diagramme de composants ci-dessous, on découvre JPA.
Page 5 - dernière modification par Pascal GRAFFION le 2014/01/12 10:28
GLG203 - TP 12 - Persistence avec JPA
Figure 2 - Diagrammes de composants avec JPA
Vue déploiement
La partie serveur est packagée dans le fichier petstore.ear (Extension Archive). Celui-ci contient notamment les deux fichiers
common.jar et server.jar (objet du domaine et DAO). Figure 3 - Diagramme de déploiement
Page 6 - dernière modification par Pascal GRAFFION le 2014/01/12 10:28
GLG203 - TP 12 - Persistence avec JPA
Important, pour que les classes DAO, qui tournent maintenant dans GlassFish, puissent accéder à la base de données et au driver
JDBC, il faut définir la source de données et installer mysql-connector-java-5.1.21-bin.jar dans GlassFish. Ceci est vérifié par la
cible checkglassfish du fichier build.xml.
Implémentation
Vous pouvez maintenant développer l'application à partir de la version précédente.
Votre travail va consister essentiellement à réécrire les classes Customer, Category, Product et Item ainsi que leurs DAO
associés.
Classes métiers
Ces classes métiers vont désormais utiliser les annotations JPA. Exemple avec l'entité Order :
@Entity
@NamedQuery(name = "Order.findAll", query="select o from Order o")
@Table(name = "T_ORDER")
public final class Order extends DomainObject implements Serializable {
// ======================================
// = Attributes =
// ======================================
@Id
@Column(name = "id", length = 10)
@TableGenerator(name="TABLE_GEN_ORDER", table="T_COUNTER", pkColumnName="name",
valueColumnName="value", pkColumnValue="Order")
@GeneratedValue(strategy=GenerationType.TABLE, generator="TABLE_GEN_ORDER")
// see http://en.wikibooks.org/wiki/Java_Persistence/Identity_and_Sequencing#Table_sequencing
private String _id;
@Column(name = "orderdate", updatable =false)
@Temporal(TemporalType.DATE)
private Date _orderDate;
@Column(name = "firstname", nullable = false, length = 50)
private String _firstname;
@Column(name = "lastname", nullable = false, length = 50)
private String _lastname;
@Embedded
private final Address _address = new Address();
@Embedded
private final CreditCard _creditCard = new CreditCard();
@OneToOne(fetch =FetchType.EAGER)
@JoinColumn(name ="customer_fk", nullable = false)
private Customer _customer;
@OneToMany (mappedBy ="_order", fetch =FetchType.EAGER, cascade =CascadeType.ALL)
private Collection<OrderLine> _orderLines;
// ...
DAOs
Les DAOs vont garder les mêmes interfaces mais leur implémentation va être grandement simplifiée.
Ainsi, la classe OrderDAO se réduit à 2 constructeurs :
public final class OrderDAO extends AbstractDataAccessObject<String, Order> {
// Used to get a unique id with the UniqueIdGenerator
private static final String COUNTER_NAME = "Order";
protected String getCounterName() {
return COUNTER_NAME;
}
public OrderDAO() {
this("petstorePU");
} public OrderDAO(String persistenceUnitName) {
super(persistenceUnitName);
}
Page 7 - dernière modification par Pascal GRAFFION le 2014/01/12 10:28
GLG203 - TP 12 - Persistence avec JPA
}
La classe OrderLineDAO possède en plus des constructeurs une seule méthode :
public Collection<OrderLine> findAllInOrder(String orderId) throws ObjectNotFoundException {
Query query = _em.createNamedQuery("OrderLine.findAllInOrder");
query.setParameter("orderId", orderId);
List<OrderLine> entities = query.getResultList();
if (entities.isEmpty())
throw new ObjectNotFoundException();
return entities;
}
Cette méthode métier findAllInOrder utilise la NamedQuery findAllInOrder définie par une annotation dans l'entité
OrderLine:
@Entity
@NamedQueries( {
@NamedQuery(name = "OrderLine.findAll", query="select o from OrderLine o"),
@NamedQuery(name = "OrderLine.findAllInOrder", query="select ol from OrderLine ol where ol._order._id = :orderId")
})
@Table(name = "T_ORDER_LINE")
public final class OrderLine extends DomainObject implements Serializable {
AbstractDataAccessObject la superclasse de tous nos DAOs
La plupart des méthodes des DAO sont désormais factorisées dans la super classe AbstractDataAccessObject<K, E> paramétrée
par la clé K et l'entité E.
/**
* This class follows the Data Access Object (DAO) Design Pattern.
* It uses JPA to store entity values in a database.
* Every concrete DAO class should extends this class.
*/
public abstract class AbstractDataAccessObject<K, E> {
// ======================================
// = Attributes =
// ======================================
protected Class<E> _entityClass;
protected EntityManager _em;
protected EntityTransaction _tx;
// ======================================
// = Constructors =
// ======================================
public AbstractDataAccessObject() {
try {
ParameterizedType genericSuperclass = (ParameterizedType) getClass().getGenericSuperclass();
Type[] actualTypeArguments = genericSuperclass.getActualTypeArguments();
this._entityClass = (Class<E>) actualTypeArguments[1];
} catch (ClassCastException e) {
this._entityClass = null;
}
}
public AbstractDataAccessObject(String persistenceUnitName) {
Type superclass = getClass().getGenericSuperclass();
try {
ParameterizedType genericSuperclass = (ParameterizedType) getClass().getGenericSuperclass();
Type[] actualTypeArguments = genericSuperclass.getActualTypeArguments();
this._entityClass = (Class<E>) actualTypeArguments[1];
} catch (ClassCastException e) {
this._entityClass = null;
}
initEntityManager(persistenceUnitName);
}
private void initEntityManager(String persistenceUnitName) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory(persistenceUnitName);
_em = emf.createEntityManager();
try {
Page 8 - dernière modification par Pascal GRAFFION le 2014/01/12 10:28
GLG203 - TP 12 - Persistence avec JPA
_tx = _em.getTransaction();
} catch (Exception e) {
_tx = null;
}
}
// ...
On trouve ensuite dans cette classe des méthodes génériques :
public void persist(E entity) {
beginTransaction();
_em.persist(entity);
endTransaction();
}
public E findById(K id) throws ObjectNotFoundException {
E result;
if ( id == null )
throw new ObjectNotFoundException();
result = _em.find(_entityClass, id);
if ( result == null )
throw new ObjectNotFoundException();
return result;
}
// ...
On retrouve ensuite les méthodes présentes dans nos DAO précédents qui restent utilisées par les classes de notre couche
Service :
public final DomainObject select(final String id) throws ObjectNotFoundException {
E result = findById((K) id);
DomainObject entity = (DomainObject) result;
return (DomainObject) result;
}
public final Collection<E> selectAll() throws ObjectNotFoundException {
int beginIndex = _entityClass.getName().lastIndexOf('.');
beginIndex++;
String shortClassName = _entityClass.getName().substring(beginIndex);
Query query = _em.createNamedQuery(shortClassName + ".findAll");
List<E> entities = query.getResultList();
if (entities.isEmpty())
throw new ObjectNotFoundException();
return entities;
}
// ...
Injection des DAO dans nos EJB
L'entity manager requis par chaque DAO est injecté dans chaque EJB stateless. Il n'est pas utilisable dans le constructeur de
l'EJB (car pas encore initialisé). C'est pourquoi on définit la méthode init qui est annotée par @PostConstruct pour pouvoir
passer l'entity manager à son DAO.
@Stateless (name="CustomerSB")
public class CustomerServiceBean extends AbstractRemoteService implements CustomerService {
// ======================================
// = Attributes =
// ======================================
@PersistenceContext(unitName = "petstorePU", type = PersistenceContextType.TRANSACTION)
private EntityManager _injectedEntityManager;
private static final CustomerDAO _dao = new CustomerDAO();
// ...
@PostConstruct
public void init() {
_dao.setEntityManager(_injectedEntityManager);
}
Page 9 - dernière modification par Pascal GRAFFION le 2014/01/12 10:28
GLG203 - TP 12 - Persistence avec JPA
Recette utilisateur
La classe de test JPACustomerTJU4 permet de mieux comprendre comment fonctionnent l'entity manager et son "Persistence
context".
public final class JPACustomerTJU4 {
private static String _persistenceUnitName = "petstorePU";
private static EntityManagerFactory _emf;
private static EntityManager _em;
private static EntityTransaction _tx;
private Customer _customer;
public static junit.framework.Test suite() {
return new JUnit4TestAdapter(JPACustomerTJU4.class);
}
@BeforeClass
public static void initEntityManager() throws Exception {
_emf = Persistence.createEntityManagerFactory(_persistenceUnitName);
_em = _emf.createEntityManager();
}
@AfterClass
public static void closeEntityManager() {
_em.close();
_emf.close();
}
@Before
public void initTransactionAndManagedCustomer() {
_tx = _em.getTransaction();
_customer = new Customer(null, "Mark", "Zuckerberg");
_tx.begin();
_em.persist(_customer);
_tx.commit();
}
@After
public void removeTestedCustomer() {
if ( !_em.contains(_customer) )
return;
_tx.begin();
_em.remove(_customer);
_tx.commit();
}
//==================================
//= Test cases =
//==================================
@Test
public void find() throws Exception {
String id = _customer.getId();
assertNotNull("ID should not be null", id);
// find it from the database
Customer customerInDB = _em.find(Customer.class, id);
assertEquals(id, customerInDB.getId());
assertEquals(_customer, customerInDB);
}
@Test
public void update() throws Exception {
String id = _customer.getId();
assertNotNull("ID should not be null", id);
String newFirstname = "Marcus";
_customer.setFirstname(newFirstname);
_tx.begin();
_em.merge(_customer);
_tx.commit();
// find it from the database
Page 10 - dernière modification par Pascal GRAFFION le 2014/01/12 10:28
GLG203 - TP 12 - Persistence avec JPA
Customer customerInDB = _em.find(Customer.class, id);
assertEquals(id, customerInDB.getId());
assertEquals(newFirstname, customerInDB.getFirstname());
}
@Test
public void refresh() throws Exception {
String id = _customer.getId();
assertNotNull("ID should not be null", id);
String newFirstname = "Marcus";
_customer.setFirstname(newFirstname);
assertEquals(newFirstname, _customer.getFirstname());
_em.refresh(_customer);
assertEquals("Mark", _customer.getFirstname());
}
@Test
public void remove() throws Exception {
String id = _customer.getId();
assertNotNull("ID should not be null", id);
_tx.begin();
_em.remove(_customer);
_tx.commit();
// try to find it from the database
Customer customerInDB = _em.find(Customer.class, id);
assertEquals(null, customerInDB);
}
@Test
public void detach() throws Exception {
String id = _customer.getId();
assertNotNull("ID should not be null", id);
assertTrue(_em.contains(_customer));
_em.detach(_customer);
assertFalse(_em.contains(_customer));
// find it from the database
Customer customerInDB = _em.find(Customer.class, id);
assertEquals(id, customerInDB.getId());
// set _customer managed again
_customer = customerInDB;
}
@Test
public void merge() throws Exception {
String id = _customer.getId();
assertNotNull("ID should not be null", id);
assertTrue(_em.contains(_customer));
_em.detach(_customer);
assertFalse(_em.contains(_customer));
String newFirstname = "Marcus";
_customer.setFirstname(newFirstname);
_tx.begin();
_em.merge(_customer);
_tx.commit();
// find it from the database
Customer customerInDB = _em.find(Customer.class, id);
assertEquals(id, customerInDB.getId());
assertEquals(newFirstname, customerInDB.getFirstname());
// set _customer managed again
_customer = customerInDB;
}
}
Tests des DAO
La classe de test AllDomainTests contient les tests de chaque DAO ainsi que des tests spécifiques JPA.
Page 11 - dernière modification par Pascal GRAFFION le 2014/01/12 10:28
GLG203 - TP 12 - Persistence avec JPA
La cible ant yaps-domain-test permet de lancer ce test en utilisant le fichier de test ${yaps.test.src.dir}/META-INF/
persistence.xml configuré pour utiliser eclipse-link.
Il est possible de lancer AllDomainTests depuis Netbeans à condition de renommer temporairement le fichier ${yaps.src.dir}/
META-INF/persistence.xml qui est malencontreusement pris en compte par NetBeans avant ${yaps.test.src.dir}/META-INF/
persistence.xml.
(Pour lancer ce test depuis Eclipse, il faudra préalablement copier ce fichier dans bin/META-INF/persistence.xml (en
supposant que bin est le "default output folder" du projet pour Eclipse)).
Recette utilisateur finale
Le fichier persistence.xml déployé dans GlassFish est différent; il est fourni dans ${yaps.src.dir}/META-INF/persistence.xml.
Ce fichier est inclus dans la librairie server.jar incluse dans l'archive déployée yapswtp12.war (ou petstore.ear dans JBoss).
Plus précisément c'est la cible ant yaps-build-server-jar qui le recopie dans le sous répertoire META-INF :
<target name="yaps-build-server-jar">
<echo message="Creates the PetStore Server Application"/>
<mkdir dir="${temp.dir}/META-INF"/>
<copy todir="${temp.dir}">
<fileset dir="${yaps.classes.dir}">
<include name="com/yaps/petstore/server/**/*.class"/>
<exclude name="com/yaps/petstore/server/service/**/*.class"/>
<exclude name="com/yaps/petstore/server/cart/*.class"/>
</fileset>
</copy>
<copy file="${yaps.src.dir}/persistence.xml" todir="${temp.dir}/META-INF"/>
<jar jarfile="${yaps.server.jar}" basedir="${temp.dir}"/>
</target>
Une fois les applications déployées dans GlassFish les tests Selenium doivent passer à 100%, validant la prise de commande
par un internaute.
Il devient également alors possible de passer la suite de tests AllExceptDomainTests.
Résumé
Références
Les cahiers du programmeurs : Java EE 5, 2nd Edition A Goncalves. Eyrolles. 2008.
EntityManager Java EE6 javadoc http://docs.oracle.com/javaee/6/api/javax/persistence/EntityManager.html
Testing Java EE components : JPA 2.0 With EclipseLink
http://www.antoniogoncalves.org/xwiki/bin/view/Article/TestingJPA
JPA implementation patterns: Data Access Objects
http://blog.xebia.com/2009/03/09/jpa-implementation-patterns-data-access-objects/
EclipseLink/Examples/JPA/JBoss Web Tutorial http://wiki.eclipse.org/EclipseLink/Examples/JPA/
JBoss_Web_Tutorial#JNDI_JTA.2Fnon-JTA_Server_DataSource_Setup
Page 12 - dernière modification par Pascal GRAFFION le 2014/01/12 10:28
Téléchargement