Object Relational Mapping - Java Persistence API

Java Persistence API, les aspects techniques (2/2)

Le manager d'entités EntityManager

L'objet EntityManager est responsable de la gestion des entités et de leurs états. Il va ainsi permettre les opérations de base offertes par le langage relationnel que sont :

L'EntityManager est donc l'objet qui va permettre au développeur de manipuler ses objets Java devenus des entités et ainsi lui permettre de les persister. Il est donc nécessaire d'obtenir une référence vers un objet EntityManager; cela s'effectue par l'appel à la méthode factory de la classe EntityManagerFactory, comme montré ci-dessous :

EntityManagerFactory factory = Persistence.createEntityManagerFactory();
EntityManager em = factory.createEntityManager();
      

On peut ensuite utiliser cette instance pour réaliser les opérations expliquées ci-dessous :

// create a new Person
Person p = new Person();
p.setFirstName("Foo");
p.setLastName("Barr");

// save p in database
em.persist(p);

// update p
p.setLastName("BAR");
em.merge(p);

// retrieve p, it's a Person with the primary key id
p = em.find(Person.class, p.getId());

// remove p
em.remove(p);
      

Le langage JPQL

Le langage JPQL est un langage de requête dont la grammaire est définie par la spécification J.P.A. Il est très proche du langage SQL dont il s'inspire fortement mais offre une approche objet. On trouve également le nom d'EJBQL dans la littérature Java, il s'agit du nom donné à ce langage dans la norme EJB2.

Voici un exemple de requête SQL repérant l'ensemble des étudiants puis sa transcription en langage JPQL :

// SQL Query
SELECT * FROM STUDENT

// JPQL Query
SELECT s FROM Student s
                

On remarque une similitude évidente, ce qui garanti une facilité d'utilisation pour toutes personnes ayant déjà pratiqué le langage SQL.

De la même façon on peut modifier ou supprimer un étudiant par les requêtes suivantes :

// Update Query
UPDATE Student s SET s.firstName = "foo", s.lastName = "bar" WHERE s.firstName = "toto"

// Delete Query
DELETE Student s WHERE s.firstName = "foo" AND s.lastName = "bar"
                

J.P.A permet l'utilisation des langages SQL et JPQL. Cependant, pour des soucis d'abstraction et de portabilité, il est fortement recommandé d'utiliser uniquement le langage de la norme J.P.A.

Les requêtes et l'objet Query

Généralités

L'objet Query va permettre de créer une requête (JPQL ou SQL). Pour pouvoir construire un objet Query, il est nécessaire de posséder une instance d'EntityManager comme démontré précédement, puis d'appeler l'une des méthodes factory suivantes en fonction du langage utilisé pour formuler la requête :

// em is an instance of EntityManager

// create a JPQL Query
Query jQuery = em.createQuery("Select s From Student s");

// create a SQL Query
Query sQuery = em.createNativeQuery("SELECT * FROM STUDENT");
                

On peut ensuite procéder à la récupération des données de la façon suivante :

// omg, unsafe cast !
List<Student> rList = jQuery.getResultList();
                

Requête paramétrée

La majorité des requêtes créées sont construites avec des conditions (clause WHERE). La façon rapide et malheureusement maladroite de créer ce type de requêtes est la concaténation de chaînes de caractères souvent obtenues en paramètre de méthodes. Cette construction pose de nombreux problèmes :

Afin de résoudre ces problèmes, il est possible et fortement conseillé d'utiliser les requêtes paramétrées. Pour cela on va insérer dans nos requêtes des éléments nommés place holder qui vont nous servir pour compléter au besoin notre requête.

Voici un exemple d'utilisation des requêtes paramétrées :

// create a query
Query q = em.createQuery("SELECT s FROM Student s WHERE s.firstname = :fs");

// set the parameter
q.setParameter("fs", "Foo");
                
Comme le montrent ces quelques lignes, l'usage des requêtes paramétrées offre de nombreux avantages :

Requête nommée

On a vu dans la section précédente l'intérêt d'utiliser les requêtes paramétrées, J.P.A permet d'ajouter encore plus de clareté et de cohérence au code grâce à l'utilisation des requêtes nommées.

Les requêtes nommées sont définies par des méta-données (via annotation ou fichier XML) et sont associées à une entité. Elles vont permettre d'attribuer un nom à une requête JPQL paramétrée ou non.

Voici un exemple de requête nommée associée à la classe Person grâce aux annotations :

@NamedQuery("Person.findAll", "Select s from Person s")
@Entity
public class Person {
...
}
                

Notre requête ainsi créée est utilisée pour construire l'objet Query associé, via la méthode adéquate de l'EntityManager :

Query nQuery = em.createNamedQuery("Person.findAll");           

L'usage des requêtes nommées est une bonne pratique qu'il est bon d'empreinter dès la création des entités et lors de la mise en place de nouvelles requêtes. En effet leur usage permet de mutualiser le code et rapproche l'objet de la requête qui l'interroge. On apporte ainsi plus de cohérence. De plus ces requêtes sont compilées lors du déploiement des entités via les modules EJB3, cela permet d'en vérifier la validité syntaxique et sémantique.

L'espace de nom utilisé pour l'identification des requêtes est partagé entre toutes les entités. Ainsi le développeur doit veiller à l'unicité des noms au risque d'être confrontré à des conflits de nommage.

Pour éviter ces désagréments, il est préférable de respecter la convention selon laquelle le nom de la requête est constitué du nom de l'objet et de la description de son résultat. Par exemple, pour lister l'ensemble des personnes on obtient le nom: Person.findAll.

Un point sur ce qu'il reste à voir

La vue rapide s'arrête ici, il reste cependant beaucoup de points à voir dans la spécification J.P.A. Voici une liste non exhaustive des points qui nécessitent un peu de lecture :

La visite des sites fournis dans la section Références devrait satisfaire le lecteur mis en appétit.