L'ORM va gérer la persistance dans la BD des objets Java en générant automatique tout le SQL nécessaire.
Hibernate est sans doute l'ORM implémentant la JPA le plus utilisé.
Historiquement Hibernate a sa propre API et il implémente aussi l'API de JPA.
Les deux API sont assez proches (en effet, l'API d'Hibernate a insipiré la JPA). L'API d'Hibernate est plus riche que la JPA.
Le parti pris de ce cours est de présenter Hibernate avec l'API JPA.
Pour l'instant, on fait Hibernate sans Spring !
POJO
public class Student { private int id; private String firstName; private String lastName; //default construct, constructeur, getters et setters }
Mapping Hibernate: fichier hibernate.cfg.xml
<hibernate-mapping> <class name = "fr.uge.jee.hibernate.Student" table = "Students"> <meta attribute = "class-description"> This class contains the details of the students </meta> <id name = "id" type = "int" column = "id"> <generator class="native"/></id> <property name = "firstName" column = "first_name" type = "string"/> <property name = "lastName" column = "last_name" type = "string"/> </class> </hibernate-mapping>
Le mapping est la description d'une table pour la classe Student et des relations entre les champs de la classe et les colonnes de la table.
@Entity @Table(name = "Students") public class Student { @Id @GeneratedValue private long id; @Column(name = "first_name") private String firstName; @Column(name = "last_name") private String lastName; ... }
@Table
ou @Column
, Hibernate utilise le nom de la classe ou du champs.La clé primaire a un rôle à part car elle n'a pas de sens dans le monde Java et qu'elle est fixée par la BD et donc par l'ORM.
public class Student { ... public Student(){} // for Hibernate public Student(String firstName, String lastName) { // for Java this.firstName = firstName; this.lastName = lastName; } ... }
EntityManager
joue le rôle d'un cache temporaire qui maintient pour certaines lignes de la base de données un unique objet Java correspondant.
Un EntityManager
encapsule une une connexion JDBC. Il ne vit donc pas très longtemps. On va en recréer souvent.
L'EntityManager n'est pas thread-safe.
EntityManager
et dont le champ @Id
est à sa valeur par défaut ou qui a été supprimé de l'EntityManager
.EntityManager
. Les modifications sur cet objet seront répercutées dans la BD (cf. slides suivants).EntityManager
mais qui y ont été (hors suppression). En particulier, ces objets ont un champ @Id
qui n'a plus sa valeur par défaut.Les EntityTransaction correspondent aux transactions au sens BD du terme.
C'est à ce niveau qu'on pourra fixer le niveau d'isolation (cf. le super cours de Mr. Francis).
Les
sont liées à un EntityTransaction
EntityManager
. C'est à l'intérieure d'une
qu'on peut va faire persister des objets, récupérer des objets correspondant à la BD, supprimer des éléments de la BD, ...EntityTransaction
Toutes les interactions avec la BD ont la même forme.
EntityManagerFactory emf = ... EntityManager em = emf.createEntityManager(); var tx = em.getTransaction(); try{ tx.begin(); ... tx.commit(); } catch (Exception e){ tx.rollback(); throw e; } finally { em.close(); }
EntityManagerFactory emf = ... EntityManager em = emf.createEntityManager(); var tx = em.getTransaction(); try{ tx.begin(); var harry = new Student("Harry","Potter"); em.persist(harry); tx.commit(); } catch (Exception e){ tx.rollback(); throw e; } finally { em.close(); }
La classe EntityManagerFactory
permet de créer des EntityManager.
La création d'une EntityManagerFactory
se fait à partir d'un ficher de configuration persistence.xml et
des classes annotées par @Entity
.
La création de l'EntityManagerFactory
est coûteuse. Elle ne doit être construite qu'une seule fois !
<persistence ...> <persistence-unit name="main-persistence-unit"> <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider> <exclude-unlisted-classes>false</exclude-unlisted-classes> <properties> <property name="jakarta.persistence.jdbc.url" value="jdbc:h2:tcp://localhost/~/h2DB"/> <property name="jakarta.persistence.jdbc.user" value="sa"/> <property name="jakarta.persistence.jdbc.password"value=""/> <property name="jakarta.persistence.schema-generation.database.action" value="create"/> </properties> </persistence-unit> </persistence>
public class PersistenceUtils { static final EntityManagerFactory ENTITY_MANAGER_FACTORY = Persistence.createEntityManagerFactory("main-persistence-unit"); static EntityManagerFactory getEntityManagerFactory(){ return ENTITY_MANAGER_FACTORY; } }
On implémente le pattern Singleton de manière thread-safe.
EntityManager.persist
(1/3)EntityManager.persist(Student student)
prend un objet student
transient et le fait persister dans la BD.
L'objet student
devient persistent.
Toute modification (par les setters) de student sera répercutée au moment du entityTransaction.commit
.
Le champ id
de student va être positionner. Attention, cela n'est garanti qu'après le entityTransaction.commit
EntityManager.persist
(2/3)EntityManagerFactory emf = PersistenceUtils.getEntityManagerFactory(); EntityManager em = emf.createEntityManager(); var tx = em.getTransaction(); try{ tx.begin(); var harry = new Student("Harry","Potter"); em.persist(harry); tx.commit(); } catch (Exception e){ tx.rollback(); throw e; } finally { em.close(); }
Ajoute un étudiant Harry Potter dans la BD. Le champs id
de l'objet harry
sera fixé par Hibernate au moment du commit
.
EntityManager.persist
(3/3)EntityManagerFactory emf = PersistenceUtils.getEntityManagerFactory(); EntityManager em = emf.createEntityManager(); var tx = em.getTransaction(); try{ tx.begin(); var harry = new Student("Harry","Potter"); em.persist(harry); harry.setFirstName("Giny"); tx.commit(); } catch (Exception e){ tx.rollback(); throw e; } finally { em.close(); }
Ajoute un étudiant Giny Potter dans la BD !
EntityManager.find
(1/2)EntityManager.find(Student.class,1L)
renvoie l'objet Student
correspondant à la ligne de la table Students ayant la clé primaire 1. S'il n'y a pas de ligne avec cette clé, la méthode renvoie null
.
L'objet renvoyé est persistent.
Toute modification (par les setters) de cet objet sera répercutée au moment du entityTransaction.commit
.
EntityManager.find
(2/2)EntityManagerFactory emf = PersistenceUtils.getEntityManagerFactory(); EntityManager em = emf.createEntityManager(); var tx = em.getTransaction(); try{ tx.begin(); var student = em.find(Student.class,1L); if (student!=null){ System.out.println(student); // print student with id 1 } tx.commit(); } catch (Exception e){ tx.rollback(); throw e; } finally { em.close(); }
EntityManager.merge
La méthode EntityManager.merge(Student student)
prend en paramètre un objet student
detached.
newstudent
depuis l'EntityManager
ou la BD pour la clé primaire student.id
student
dans newstudent
: comme newstudent
est persistent, cela modifiera la BDnewstudent
Attention à ne pas continuer à utiliser student
.
EntityManager.remove
La méthode EntityManageer.remove(student)
prend en paramètre un objet Student
persistent et supprime
la ligne correspondante dans la BD.
L'objet student n'est plus persistent. Il redevient transient.
JPA offre un langage de requête, nommé JPQL, proche de SQL mais présente plusieurs avantages:
var q = "SELECT s from Student s where s.firstName='Harry'"; TypedQuery<Student> query = em.createQuery(q,Student.class); List<Student> results = query.getResultList(); Student resutl = query.getSingleResult();
La requête JPQL utilise la classe Student et non la table Students.
On peut faire des requêtes en SQL avec createNativeQuery
.
Code vulnérable aux injections:
var firstName = ... // recupérer d'une form HTML var q = "SELECT s FROM Student s WHERE s.firstName='"+firstName+"'"; TypedQuery<Student> query = em.createQuery(q,Student.class); List<Student> res = query.getResultList();
var firstName = "Harry' AND s.grade='10"; var q = "SELECT s FROM Student s WHERE s.firstName='"+firstName+"'"; TypedQuery<Student> query = em.createQuery(q,Student.class); List<Student> res = query.getResultList();
La requête JPQL devient alors:
SELECT s FROM Student s WHERE s.firstName='Harry' AND s.grade='10'
On peut accéder à des informations de la BD alors que ce n'était pas prévu.
On suppose ici qu'on a rajouté un champ grade à la classe Student
.
var firstName = ... // var q = "SELECT s FROM Student s WHERE s.firstName= :firstname"; TypedQuery<Student> query = em.createQuery(q,Student.class); query.setParameter("firstname",firstName); List<Student> res = query.getResultList();
Les caractères spéciaux de firstName
sont échappés par le framework.
Pas d'injection possible!