Passer au contenu principal
Mapping Objet Relationnel
JPA, Hibernate et Spring Data
• Réalisé par:
BOUGANFOU L’houssaine
Travailler dans les deux univers que sont l'orienté objet et la
base de données relationnelle peut être lourd et consommateur en
temps dans le monde de l'entreprise d'aujourd'hui.
Hibernate est un outil de mapping objet/relationnel pour le
monde Java.
Le terme mapping objet/relationnel (ORM) décrit la
technique consistant à faire le lien entre la représentation objet des
données et sa représentation relationnelle basée sur un schéma
SQL.
Introduction
2
 Une Entity est l'unité de persistance
○ Elle représente (avec quelques nuances) une ligne dans une table
 Le EntityManager est la classe fournissant l'accès à l'API. Elle permet
de :
○ Gérer les Entity
○ Faire des requêtes
Architecture de JPA
3
 Une Entity est une classe Java « normale » qui est annotée avec un
marqueur @Entity
 Une Entity a obligatoirement un champ « identifiant », qui correspond
à la clé primaire en base de données
 Les liens entre les entités sont gérés par des Collection
○ @OneToMany @ManyToMany …
○ Ces liens peuvent être unidirectionnels ou bidirectionnels
(auquel cas il faudra utiliser l'attribut mappedBy pour spécifier
la foreign key)
4
Les entités
 Un EntityManager gère la persistance
○ C'est une interface, l'implémentation étant fournie par le
fournisseur JPA (Hibernate dans notre cas)
○ C'est lui qui réalise la « magie » et sauvegarde, met à jour ou
efface automatiquement les Entity
 Pour cela il va utiliser deux mécanismes principaux
○ La génération de proxy
○ L'utilisation d'un cache de premier niveau
5
Le EntityManager
 Le Lazy Loading est largement utilisé par JPA
○ Il permet de ne charger que les Entity nécessaires
○ Sinon les liens entre Entity feraient remonter l'ensemble des
données de la base
 Pour cela JPA génère des Proxy, qui remplacent les « vraies » Collection et
les « vraies » Entity
 Ces proxy sont générés via une librairie de manipulation de bytecode
○ CGLIB dans les anciennes versions de Hibernate, Javassist dans les
nouvelles
○ Méfiez-vous des instanceOf sur des Entity
○ Les Entity ne doivent pas être déclarées final, afin de pouvoir
être modifiées par la librairie 6
Génération de Proxy
 JPA utilise 2 niveaux de cache
 Le cache L1 est nécessaire à son bon fonctionnement
○ Il correspond à la transaction en cours
○ Pour cela, deux appels successifs à em.find(Person.class, login)
ne donneront lieu qu'à une seule requête SQL (correct du point de
vue transactionnel, avec l'isolation)
○ C'est dans ce cache L1 que JPA « enregistre » les modifications
effectuées sur les Entity qu'il manage
○ A la fin d'une transaction, le cache est flushé → il est détruit lors
du commit
○ Si vous voulez forcer l'exécution des requêtes SQL en milieu de
transaction, utilisez em.flush()
 Intéressant pour débugger
 Utile si vous utilisez du JDBC en parallèle
7
Niveaux de cache L1 et L2
 Le cache L2 est optionnel
○ Il n'est pas activé par défaut
○ C'est généralement ce que les gens appellent « le cache
Hibernate », car ils ne font pas la distinction L1 et L2
○ De multiples implémentations sont disponibles : ehcache est la
plus populaire, Infinispan est maintenant le cache par défaut
○ C'est un cache partagé entre toutes les transactions
(également appelé Shared Cache)
 Hibernate propose également un cache des requêtes
 La bonne utilisation de ce cache L2 est essentielle pour
avoir une application performante
○ Cela signifie de coder son application spécifiquement pour bien
l'utiliser
8
Niveaux de cache L1 et L2
 Hibernate utilise toujours la stratégie de lock de la base de données
○ Sans lock, il est probable que vous soyez dans une situation où « le
dernier commit gagne », ce qui peut porter à confusion pour vos
utilisateurs finaux
 La meilleure option pour une application performante et scalable est
d'utiliser l'optimistic locking
○ Une vérification de version est réalisée par Hibernate à chaque
commit, et peut résulter dans un OptimisticLockException en cas de
conflit
○ Vous pouvez pour cela utiliser un champ de version, qui peut être de
type int, short, long ou timestamp
○ Si vous n'utilisez pas de numéro de version, Hibernate peut valider
 sur l'ensemble des champs de votre Entity, ou
 uniquement sur les champs modifiés
Dans les deux cas cette solution est plus lente, non standard, et vous empêche
d'avoir des Entity détachées
9
Le Locking
 Le champ de version est marqué avec l'annotation @Version
public class Person {
@ID String login;
@Version long version;
}
 Il rajoute une clause SQL spécifique :
UPDATE Person SET …, version = version +1
WHERE id = ?
AND version = readVersion;
 Utiliser un timestamp
○ est intéressant car on a alors une date de mise à jour,
○ est plus risqué (il peut y avoir deux updates avec le même
timestamp, et en cluster vous augmentez ces risques) 10
Le Locking
 Utiliser en priorité la configuration JPA
○ Ne pas utiliser de configuration Hibernate sauf pour les cas
« hors standard »
 Coder correctement ses objets Java : clé primaire, méthodes equals et
hashcode...
 Etudier le métier et faire un modèle correct par rapport aux besoins :
éviter les relations bidirectionnelles si elles n'ont pas de sens
 Utiliser le lazy-loading
 Utiliser les bons types de Collection pour les associations
11
Consignes générales sur la modélisation
public List<Produit> produitsParMC(String mc) {
List<Produit> produits=new ArrayList<Produit>();
Class.forName("com.mysql.jdbc.Driver");
Connection conn=DriverManager.getConnection
("jdbc:mysql://localhost:3306/DB_CAT","root","");
PreparedStatement ps=conn.prepareStatement("SELECT *
FROM PRODUITS WHERE DESIGNATION like ?");
ps.setString(1, mc);
ResultSet rs=ps.executeQuery();
while(rs.next()){
Produit p=new Produit();
p.setId(rs.getLong("ID"));
p.setDesignation(rs.getString("DESIGNATION"));
p.setPrix(rs.getDouble("PRIX"));
p.setQuantite(rs.getInt("QUANTITE"));
produits.add(p);
}
return produits;
}
Mapping Objet Relationnel
12
o Le but d'Hibernate est de libérer le développeur de 95 pourcent des
tâches de programmation liées à la persistance des données
communes.
o Hibernate assure la portabilité de votre application si vous changer de
SGBD.
o Hibernate propose au développeur des méthodes d’accès aux bases
de données plus efficace ce qui devrait rassurer les développeurs.
But de Hibernate
13
o SessionFactory est une interface. SessionFactory peut être
créé en fournissant un objet Configuration, qui contiendra tous
les détails de propriété liés à la base de données extraits du
fichier hibernate.cfg.xml ou du fichier
hibernate.properties. SessionFactory est une fabrique d'objets
Session.
o Nous pouvons créer une implémentation SessionFactory par
base de données dans n'importe quelle application. Si votre
application fait référence à plusieurs bases de données, vous
devez créer une SessionFactory par base de données.
Session Factory
14
o Contrairement à SessionFactory, l'objet Session sera créé à la
demande. La session est un objet léger. La session fournit une
connectivité physique entre votre application et la base de
données. La session sera établie chaque fois que votre application
souhaite faire quelque chose avec la base de données. L'objet
Session sera fourni par l'objet SessionFactory. Tous les objets
persistants seront enregistrés et récupérés via l'objet Session. L'objet
de session doit être détruit après l'avoir utilisé.
o Le cycle de vie d'une session est limité par le début et la fin d'une
transaction logique. La fonction principale de la session est d'offrir
des opérations de création, de lecture et de suppression pour les
instances de classes d'entités mappées.
Session
15
Exemple
public class HibernateUtil {
private static SessionFactory sessionFactory = null;
static {
try{
loadSessionFactory();
}catch(Exception e){
System.err.println("Exception while initializing hibernate util.. ");
e.printStackTrace();
}
}
public static void loadSessionFactory(){
Configuration configuration = new Configuration();
configuration.configure("/j2n-hibernate.cfg.xml");
configuration.addAnnotatedClass(Employee.class);
ServiceRegistry srvcReg = new
StandardServiceRegistryBuilder().applySettings(configuration.getProperties()).build();
sessionFactory = configuration.buildSessionFactory(srvcReg);
}
public static Session getSession() throws HibernateException {
Session retSession=null;
try {
retSession = sessionFactory.openSession();
}catch(Throwable t){
System.err.println("Exception while getting session.. ");
t.printStackTrace();
}
if(retSession == null) {
System.err.println("session is discovered null");
}
return retSession;
}
}
16
JPA est une spécification créée par Sun pour standardiser le mapping
Objet relationnel.
JPA définit un ensemble d’interfaces, de classes abstraites et
d’annotations qui permettent la description du mapping objet relationnel
Il existe plusieurs implémentation de JPA:
◦ Hibernate
◦ Toplink
◦ IBatis
◦ EclipseLink
L’utilisation de JPA permet à votre application d’être indépendante du
Framework ORM utilisé
Première approche de l’architecture d’Hibernate
17
o Hibernate permet d’assurer la persistance
des objets de l’application dans un entrepôt
de données.
o Cet entrepôt de données est dans la majorité
des cas une base de données relationnelle,
mais il peut être un fichier XML.
o Le mapping des objets est effectuée par
Hibernate en se basant sur des fichiers de
configuration en format texte ou souvent
XML.
Première approche de l’architecture d’Hibernate
18
Il existe deux moyens pour mapper les entités :
◦ Créer des fichier XML de mapping
◦ Utiliser les Annotations JPA
I 'utilisation des annotations JPA laisse votre code indépendant
de Hibernate.
La création des fichiers XML de mapping a l’avantage de
séparer le code java du mapping objet relationnel.
Mapping Objet Relationnel des entités
19
@Table
• Préciser le nom de la table concernée par le mapping. Par défaut c’est le nom de la classe
qui sera considérée
@Column
• Associer un champ de la colone à la propriété. Par défaut c’est le nom de la propriété qui
sera considérée.
@Id
• Associer un champ de la table à la propriété en tant que clé primaire
@GeneratedValue
• Demander la génération automatique de la clé primaire au besoin
@Basic
• Représenter la forme de mapping la plus simple. Cette annotation est utilisée par défaut
@Transient
• Demander de ne pas tenir compte du champ lors du mapping
@OneToMany, @ManyToOne
• Pour décrire une association de type un à plusieurs et plusieurs à un
@JoinedColumn
• Pour décrire une clé étrangère dans une table
@ManyToMany
• Pour décrire une association plusieurs à plusieurs
Etc…
Quelques annotations JPA de Mapping des Entités
20
Entité Produit
package dao;
import java.io.Serializable; import javax.persistence.*;
@Entity
@Table(name="PRODUITS")
public class Produit implements Serializable {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Column(name="REF")
private Long reference;
@Column(name="DES")
private String designation;
private double prix;
private int quantite;
public Produit(String designation, double prix, int quantite) {
this.designation = designation; this.prix = prix; this.quantite = quantite;
}
public Produit() { }
// Getters et Setters
} 21
Configuration de l’unité de persistance : persistence.xml
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.1" xmlns="http://xmlns.jcp.org/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence
http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd ">
<persistence-unit name="UP_CAT" >
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
<properties>
<property name="hibernate.connection.url" value="jdbc:mysql://localhost:3306/CAT"
<property name="hibernate.connection.username" value="root"/>
<property name="hibernate.connection.password" value=""/>
<property name="hibernate.connection.driver_class" value="com.mysql.jdbc.Driver"/
<property name="hibernate.hbm2ddl.auto" value="update"/>
<property name="hibernate.show_sql" value="true"/>
<property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect"/>
</properties>
</persistence-unit>
</persistence>
22
Gestion des entités par EntityManager
EntityManager est une interface définie dans JPA.
Chaque framework ORM possède sa propre implémentation de cette interface.
EntityManager définit les méthodes qui permettent de gérer le cycle de vie de la
persistance des Entity.
o La méthode persist() permet rendre une nouvelle instance d’un EJB Entity
persistante. Ce qui permet de sauvegarder sont état dans la base de données
o La méthode find() permet de charger une entité sachant sa clé primaire.
o La méthode createQuery() permet de créer une requête qui permet charger une
liste d’entités selon des crières.
o La méthode remove() permet de programmer une entité persistance pour la
suppression.
o La méthode merge() permet de rendre une entité détachée persistante.
23
Gestion des entités
package dao;
import java.util.List;
import javax.persistence.*;
public class CatalogueDaoImpl implements ICatalogueDao {
/* Déclaration de l’objet EntityManager qui permet de gérer les
entités*/
private EntityManager entityManager;
/* Constructeur */
public CatalogueDaoImpl() {
/* Création de l’objet Entity Manager Factory */
EntityManagerFactory entityManagerFactory=
Persistence.createEntityManagerFactory("UP_CAT");
/* Création de l’objet Entity Manager */
entityManager=entityManagerFactory.createEntityManager();
}
24
Ajouter un produit : La méthode persist()
public void addProduit(Produit p) {
/* Création d’une transaction */
EntityTransaction transaction=entityManager.getTransaction();
/* Démarrer la transaction */
transaction.begin();
try {
/* enregister le produit p dans la base de données */
entityManager.persist(p);
/* Valider la transaction si tout se passe bien */
transaction.commit();
} catch (Exception e) {
/* Annuler la transaction en cas d’exception */
transaction.rollback();
e.printStackTrace();
}
} 25
Avec Spring
@Transactional
public void addProduit(Produit p) {
entityManager.persist(p);
}
26
Consulter tous les produits : createQuery()
public List<Produit> listProduits() {
Query query=entityManager.createQuery("select p from Produit p");
return query.getResultList();
}
◦ Pour sélectionner des données à partir de la base de données, on peut créer
un objet Query en utilisant la méthode createQuery() de entityManager.
◦ La requête est spécifiée en utilisant le langage de requêtes JPA appelé HQL
ou JPA QL.
◦ HQL ressemble à SQL, sauf que au lieu de des tables et des relations, entre
les tables, on utilise les classes et les relations entre les classes.
◦ En fait, avec JPA, quant on fait la programmation orientée objet, on n’est pas
sensé connaitre la structure de la base de données, mais plutôt on connait le
diagramme de classes des différentes entités.
◦ C’est Hibernate qui va traduire le HQL en SQL. Ceci peut garantir à notre
application de fonctionner correctement quelque soit le type de SGBD utilisé
27
Consulter tous les produits par mot clé :
createQuery()
public List<Produit> produitsParMC(String mc) {
Query query=entityManager.createQuery("select p from Produit p
where p.designation like :x");
query.setParameter("x", "%"+mc+"%");
return query.getResultList();
}
28
Consulter un produit : Méthode find()
public Produit getProduit(Long idProduit) {
Produit p=entityManager.find(Produit.class, idProduit);
return p;
}
◦ Pour sélectionner un objet sachant son identifiant (clé primaire), on
utilise la méthode find() de l’objet entityManager.
◦ Si l’objet n’existe pas, cette méthode retourne null.
29
Mettre à jour un produit : Méthode merge()
public void updateProduit(Produit p) {
entityManager.merge(p);
}
Pour mettre à jour un objet édité et modifier, on peut
utiliser la méthode merge () de entityManager
30
Cycle de vie d’une Entity JPA
31
Objet Persistant
o Un objet persistant est un objet qui possède son image
dans le datastore et dont la durée de vie est
potentiellement infinie.
o Pour garantir que les modifications apportées à un objet
sont rendues persistantes, c’est-à-dire sauvegardées,
l’objet est surveillé par un «traqueur» d’instances
persistantes. Ce rôle est joué par le gestionnaire d’entités.
32
Objet Transient
o Un objet transient est un objet qui n’a pas son
image stockée dans le datastore.
o Il s’agit d’un objet « temporaire », qui meurt
lorsqu’il n’est plus utilisé par personne. En Java,
le garbage collector le ramasse lorsque aucun
autre objet ne le référence.
33
Objet Détaché
o Un objet détaché est un objet qui possède son
image dans le datastore mais qui échappe
temporairement à la surveillance opérée par le
gestionnaire d’entités. Pour que les modifications
potentiellement apportées pendant cette phase de
détachement soient enregistrées, il faut effectuer
une opération manuelle pour merger cette
instance au gestionnaire d’entités. 34
Objet Retiré
o Un objet retiré est un objet actuellement géré par
le gestionnaire d’entités mais programmé pour ne
plus être persistant. À la validation de l’unité de
travail, un ordre SQL delete sera exécuté pour
retirer son image du datastore.
35
Qu'est-ce que Bean Validation ?
Bean Validation est une spécification (JSR-303)
L’implémentation de référence est Hibernate Validator, un sous-projet
Hibernate
Bean Validation permet de valider des Java Beans par l’ajout
d’annotations
• Cela permet de «renforcer» les entités Hibernate
 Ces entités ne se contentent plus de stocker simplement des
données, elles peuvent les valider
• Bean Validation est également utilisable en dehors d’Hibernate
36
Hibernate Validator
Hibernate Validator est l’implémentation de référence
Hibernate Validator propose un certain nombre d’annotations qui ne
sont pas présentes dans la spécification
Annotations Hibernate Validator
@Email
@URL
@CreditCardNumber
@Range
37
JPA, Hibernate, Spring Data
o Spring Data est un module de Spring qui a déjà créé des interfaces
génériques et des implémentations génériques qui permettent de
gérer les entités JPA.
o En utilisant Spring Data, vous n’aurez plus besoin de faire appel à
l’objet EntityManager pour gérer la persitence. Spring Data le fait à
votre place.
o Spring Data nous évite de créer les interfaces et les implémentation
JPA de la couche DAO.
o Il suffit de créer une interface qui hérite de l’interface
JPARepository pour hériter toutes les méthodes classiques qui
permettent de gérer les entités JPA.
o En cas de besoin, vous avez la possibilité d’ajouter d’autres
méthodes en les déclarant à l’intérieur de l’interface
JPARepository, sans avoir besoin de les implémenter. SpringData
le fera à votre place.
38
Exemple d’interface JPARepository
package org.sid.dao;
import java.util.List;
import org.sid.entities.Produit;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
public interface ProduitRepository extends
JpaRepository<Produit, Long> {
public List<Produit> findByDesignation(String des);
}
39
Exemple d’interface JPARepository
package org.sid.dao;
import java.util.List;
import org.sid.entities.Produit;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
public interface ProduitRepository
extends JpaRepository<Produit, Long> {
@Query("select p from Produit p where p.designation
like :x and p.prix>:y")
public List<Produit> chercherProduits(
@Param("x")String mc,
@Param("y")double prixMin);
}
40
Merci pour votre attention
41