try(EntityManager entityManager = emf.createEntityManager()){String lastname =(String) entityManager.createNativeQuery("SELECT lastname FROM CUSTOMER WHERE id = :id").setParameter("id",1).getSingleResult();System.out.println(lastname);}
try(EntityManager entityManager = emf.createEntityManager()){long result =(Long) entityManager.createNativeQuery(""" SELECT COUNT(1) FROM CUSTOMER""").getSingleResult(); log.info("Customers count: %d".formatted(result));}
18:40:52.050 [IJava-executor-0] INFO notebook -- Customers count: 2000
String name ="Smith";try(EntityManager entityManager = emf.createEntityManager()){ name =(String)entityManager.createNativeQuery(""" SELECT LASTNAME FROM CUSTOMER ORDER BY RANDOM() LIMIT 1""").getSingleResult();}log.info("Nom d'une personne aléatoire: {}",name);
18:40:52.161 [IJava-executor-0] INFO notebook -- Nom d'une personne aléatoire: Kautzer
SQL Natif (paramétres)
Les requêtes peuvent être paramétrées (? et setParameter(…)).
//Variable name (String) contains an existing name try(EntityManager entityManager = emf.createEntityManager()){Query query = entityManager.createNativeQuery(""" SELECT * FROM CUSTOMER WHERE LASTNAME = :lastname""", Customer.class);List<Customer> customers = query.setParameter("lastname", name).setMaxResults(3).getResultList(); log.info("3 firsts customers whose name is %s".formatted(name)); customers.stream().forEach(System.out::println);}
//Variable name (String) contains an existing name try(EntityManager entityManager = emf.createEntityManager()){Query query = entityManager.createNativeQuery(""" SELECT * FROM CUSTOMER WHERE LASTNAME = ?""", Customer.class);List<Customer> customers = query.setParameter(1, name).setMaxResults(3).getResultList(); log.info("3 firsts customers whose name is %s".formatted(name)); customers.stream().forEach(System.out::println);}
// Liste d'entitésTypedQuery<Customer> query = entityManager.createQuery("SELECT c FROM Customer c WHERE c.id > :minId", Customer.class);List<Customer> customers = query.setParameter("minId",10).getResultList();
// Résultat uniqueCustomer customer = entityManager.createQuery("SELECT c FROM Customer c WHERE c.lastname = :lastname", Customer.class).setParameter("lastname", name).setMaxResults(1).getSingleResult();log.info("Customer: %s".formatted(customer.getEmail()));
18:40:52.582 [IJava-executor-0] INFO notebook -- Customer: Charlie.Kautzer@legros.co
Éléments de syntaxe JPQL
Clauses principales:
SELECT, FROM, WHERE
GROUP BY, HAVING
ORDER BY
Expressions de chemin:
Navigation: customer.address.city
Collections: customer.orders
Clés composites: employee.id.department
Opérateurs:
Comparaison: =, >, <, >=, <=, <>, BETWEEN, LIKE, IN
// Chemin composé"SELECT c FROM Customer c WHERE c.address.country = :country"// Opérateurs collections"SELECT c FROM Customer c WHERE c.orders IS NOT EMPTY"// Fonctions"SELECT CONCAT(c.firstName, ' ', c.lastName) FROM Customer c"// Sous-requêtes"SELECT c FROM Customer c WHERE SIZE(c.orders) > (SELECT AVG(SIZE(c2.orders)) FROM Customer c2)"
Exemple d’éxecution de requêtes JPQL
//Variable name (String) contains an existing name TypedQuery<Customer> query = entityManager.createQuery(""" SELECT c FROM Customer c WHERE c.lastname = :name""", Customer.class);query.setParameter("name", name).setMaxResults(3).getResultStream().map(c->c.getFirstname()+" "+c.getLastname()).forEach(System.out::println);
Charlie Kautzer
Nicki Kautzer
Amanda Kautzer
Projections JPQL
Sélection partielle des attributs
Construction de DTOs
Agrégations et groupements
Types de retour:
Object[] pour projections multiples
Type simple pour projection unique
DTO avec constructeur
// DTO avec constructeurList<CustomerDTO> dtos = em.createQuery("SELECT new com.example.CustomerDTO(c.name, c.age) "+"FROM Customer c", CustomerDTO.class).getResultList();// Projection multipleList<Object[]> results = em.createQuery("SELECT c.name, c.age FROM Customer c").getResultList();
Exemple de projection sur un DTO
package fr.univtln.bruno.demos.jpa.hello.samples.ex_dto;
/**
* A DTO (Data Transfer Object) for displaying customer information.
*/
public record CustomerDisplayDTO(String firstname, String lastname) {
}
//Variable name (String) contains an existing name TypedQuery<CustomerDisplayDTO> query = entityManager.createQuery(""" SELECT new fr.univtln.bruno.demos.jpa.hello.samples.ex_dto.CustomerDisplayDTO (c.firstname, c.lastname) FROM Customer c WHERE c.lastname = :name""", CustomerDisplayDTO.class);query.setParameter("name", name).setMaxResults(3).getResultStream().forEach(System.out::println);
// Groupement avec having"SELECT c.country, COUNT(c), AVG(c.age) FROM Customer c GROUP BY c.country HAVING COUNT(c)>:minCustomers"// Sous-requêtes"SELECT c FROM Customer c WHERE (SELECT COUNT(o) FROM c.orders o)>:orderCount"
Exemple de comptage
long customerCount=(long)entityManager.createQuery(""" SELECT COUNT(c) FROM Customer c""").getSingleResult();System.out.println("There is %s customers.".formatted(customerCount));
There is 2000 customers.
Jointures JPQL
Types de jointures disponibles:
INNER JOIN
LEFT JOIN
JOIN FETCH (chargement eager)
// Inner join avec fetch"SELECT c FROM Customer c JOIN FETCH c.orders o WHERE o.total > :total"// Left join"SELECT DISTINCT c FROM Customer c LEFT JOIN c.orders o WHERE o.status = :status"
Named Queries
Requêtes définies au niveau de l’entité
Validées au démarrage de l’application
Réutilisables et maintenables
Existent pour toutes les formes de requêtes (JPQL, SQL et procédures stockées).
@Entity@NamedQueries({@NamedQuery( name ="Customer.findByStatus", query ="SELECT c FROM Customer c WHERE c.status = :status"),@NamedQuery( name ="Customer.countByCountry", query ="SELECT c.country, COUNT(c) FROM Customer c GROUP BY c.country")})
Daniell Christiansen (LEAD)
Sharan Carter (LEAD)
Catrina Conn (LEAD)
Entity Graphs
Contrôle fin du chargement des associations
Types de graphes:
@NamedEntityGraph au niveau entité
Entity graphs dynamiques
LOAD vs FETCH
LOAD graph: garde les paramètres de chargement par défaut + ceux du graph
FETCH graph: charge UNIQUEMENT ce qui est spécifié dans le graph
Named Entity Graph
Déclaré au niveau de l’entité
@Entity@NamedEntityGraph( name ="Customer.withOrders",// Nom unique du graph attributeNodes ={// Liste des associations à charger@NamedAttributeNode("orders")// Charge l'association orders})publicclass Customer {@OneToMany(mappedBy ="customer")privateSet<Order> orders;// Association orders vers Order}// Utilisation du Named Entity Graph// Le graph est récupéré par son nom et appliqué viai un hintEntityGraph<?> graph = em.getEntityGraph("Customer.withOrders");List<Customer> customers = em.createQuery("SELECT c FROM Customer c", Customer.class).setHint("jakarta.persistence.fetchgraph", graph)// Active le graph.getResultList();
Entity Graph dynamique
Alternative flexible, créé programmatiquement
EntityGraph<Customer> graph = em.createEntityGraph(Customer.class);graph.addAttributeNodes("orders");// Premier niveaugraph.addSubgraph("orders").addAttributeNodes("items");// Second niveau
Performance et Optimisation JPA
Gestion du cache:
Cache L1 (EntityManager): automatique, par transaction
Cache L2 (SessionFactory): partagé, configurable
Query cache: résultats des requêtes
Stratégies de chargement:
LAZY: chargement à la demande
EAGER: chargement immédiat
JOIN FETCH: optimisation des requêtes
Entity Graphs: contrôle fin
Pagination et batching:
setFirstResult/setMaxResults
hibernate.jdbc.batch_size
Requêtes par lots (bulk operations)
Exemple d’optimisation de requêtes
// filepath: /examples/performance/QueryOptimization.java// Cache de requêteQuery query = em.createQuery(jpql).setHint("jakarta.persistence.cache.storeMode", CacheStoreMode.USE).setHint("jakarta.persistence.cache.retrieveMode", CacheRetrieveMode.USE);// Pagination optimiséeList<Customer> customers = em.createQuery(jpql, Customer.class).setFirstResult(offset).setMaxResults(pageSize).setHint("org.hibernate.readOnly",true).getResultList();// Opération par lotsint updated = em.createQuery("UPDATE Customer c SET c.status = :status WHERE c.type = :type").setParameter("status","VIP").setParameter("type","PREMIUM").executeUpdate();
publicabstractclass JpaRepository<T, ID>implements GenericRepository<T, ID>{//A fournir dans le constructeur ou autre (cf. CDI ou Spring)protected EntityManager em;//A fournir ou à déduireprivatefinalClass<T> entityClass;protectedJpaRepository(Class<T> entityClass, EntityManager entityManager){this.em= entityManager;this.entityClass= entityClass;}@Overridepublic Optional<T>findById(ID id){return Optional.ofNullable(em.find(entityClass, id));}@OverridepublicList<T>findAll(int pageNumber,int pageSize){//Utiliser des named queries ou la criteria APIString jpql ="SELECT e FROM "+ entityClass.getSimpleName()+" e ORDER BY e.id";return em.createQuery(jpql, entityClass).setFirstResult(pageNumber*pageSize).setMaxResults(pageSize).getResultList();}@Overridepublic T save(T entity){ em.getTransaction().begin();if(em.contains(entity)){ entity = em.merge(entity);}else{ em.persist(entity);} em.getTransaction().commit();return entity;}@Overridepublicvoiddelete(T entity){ em.getTransaction().begin(); em.remove(entity); em.getTransaction().commit();}}
Repository JPA avec requêtes personnalisées
publicclass CustomerRepository extends JpaRepository<Customer,Long>{publicCustomerRepository(EntityManager entityManager){super(Customer.class, entityManager);}publicList<Customer>findByStatus(int pageNumber,int pageSize, Customer.Status status){return em.createQuery("SELECT c FROM Customer c WHERE c.status = :status ORDER BY c.id", Customer.class).setParameter("status", status).setFirstResult(pageNumber*pageSize).setMaxResults(pageSize).getResultList();}}