Ressources
Définition
The Java Persistence Criteria API is used to define dynamic queries through the construction of object-based query definition objects, rather than use of the string-based approach of JPQL. The criteria API allows dynamic queries to be built programmatically offering better integration with the Java language than a string-based 4th GL approach.
Dans le passé, Hibernate proposait également sa propre API propriétaire Criteria. Elle est obsolète dans Hibernate 5 et vous devez l’éviter lors de la mise en œuvre de nouveaux cas d’utilisation. Since Hibernate 5.2, the Hibernate Criteria API is deprecated, and new development is focused on the JPA Criteria API.. L’ensemble des avantages de l’une et autre API est l’excellant article Migrating from Hibernate’s to JPA’s Criteria API
Exemple
Le code suivant retourne toutes les instances de type Pet. Nous détaillerons dans la page suivante le code ci-dessous
EntityManagerFactory emf = Persistence.createEntityManagerFactory("MaBaseDeTestPU");
EntityManager em = emf.createEntityManager();
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Pet> cq = cb.createQuery(Pet.class);
Root<Pet> pet = cq.from(Pet.class);
cq.select(pet);
TypedQuery<Pet> q = em.createQuery(cq);
List<Pet> allPets = q.getResultList();C’est l’équivalent de la requête
SELECT * FROM PetL’exemple ci-dessus est simpliste et l’utilisation de la méthode suivante aurait suffit
List<Pet> = em.createQuery("SELECT p FROM Pet p", Pet.class).getResultList();Pourquoi l’utiliser ?
Très bonne remarque ! En effet, jusqu’à présent, les opérations de base sur les entités (persist, merge, remove) ne permettaient pas de contrôler les conditions WHERE lors de leur exécution. Plusieurs solutions sont possibles :
- faire un filtre directement en Java
- utiliser JPQL + méthode
createQuery - utiliser l’API Criteria
WHERE en Java
Imaginons que nous voulons récupérer tous les animaux de compagnie d’un propriétaire donné, de type “chien” et âgés de moins de 5 ans, triés par ordre alphabétique.
// Récupération d'un propriétaire par son ID
Owner owner = em.find(Owner.class, ownerId);
// Récupération des animaux de compagnie du propriétaire
List<Pet> pets = new ArrayList<>();
for (Pet pet : owner.getPets()) {
if (pet.getType().equals("dog") && pet.getAge() < 5) {
pets.add(pet);
}
}
// Trier les animaux par ordre alphabétique
pets.sort(Comparator.comparing(Pet::getName));- Il faut écrire du code Java pour filtrer et trier les résultats.
- Les performances peuvent être médiocres, surtout si la collection d’animaux de compagnie est volumineuse.
- Si la logique de filtrage devient plus complexe, le code devient rapidement difficile à maintenir.
De plus, en SQL nous avons la possibilité d’écrire une requête qui répond à ce besoin.
Résultat Il y aura deux appel SQL (FETCH.LAZY)
- un premier pour récupérer le
owner - et le second lorsqu’on execute la méthode
owner.getPets()
[Hibernate]
select
o1_0.id,
o1_0.name
from
owners o1_0
where
o1_0.id=?
[Hibernate]
select
p1_0.owner_id,
p1_0.id,
p1_0.age,
p1_0.name,
p1_0.type
from
pets p1_0
where
p1_0.owner_id=?
WHERE avec JPQL
Une alternative consiste à écrire une requête SQL préparée puis utiliser createQuery (voir EntityManager Operation) puis ensuite saisir les paramètres
Long ownerId = 1L; // Replace with the actual owner ID
String jpql = "SELECT p FROM Pet p WHERE p.type = :type AND p.age < :age AND p.owner.id = :ownerId ORDER BY p.name ASC";
TypedQuery<Pet> query = em.createQuery(jpql, Pet.class);
query.setParameter("type", "dog");
query.setParameter("age", 5);
query.setParameter("ownerId", ownerId);
List<Pet> filteredPets = query.getResultList();
for (Pet pet : filteredPets) {
System.out.println(pet);
}Résultat Un seul appel SQl
[Hibernate]
select
p1_0.id,
p1_0.age,
p1_0.name,
p1_0.owner_id,
p1_0.type
from
pets p1_0
where
p1_0.type=?
and p1_0.age<?
and p1_0.owner_id=?
order by
p1_0.nameWHERE avec API Criteria
Et finalement une autre option est d’utiliser l’API Criteria
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Pet> cq = cb.createQuery(Pet.class);
Root<Pet> pet = cq.from(Pet.class);
Join<Pet, Owner> owner = pet.join("owner");
cq.select(pet)
.where(
cb.equal(pet.get("type"), "dog"),
cb.lessThan(pet.get("age"), 5),
cb.equal(owner.get("id"), ownerId)
)
.orderBy(cb.asc(pet.get("name")));
TypedQuery<Pet> q = em.createQuery(cq);
List<Pet> filteredPets = q.getResultList();- La construction de la requête est modulaire et peut être facilement étendue ou modifiée.
- Les performances sont généralement meilleures car la requête est optimisée par le fournisseur JPA.
Résultat Un seul appel SQl
[Hibernate]
select
p1_0.id,
p1_0.age,
p1_0.name,
p1_0.owner_id,
p1_0.type
from
pets p1_0
where
p1_0.type=?
and p1_0.age<?
and p1_0.owner_id=?
order by
p1_0.nameComplément : JOIN FETCH
On notera que dans les deux dernière solution nous n’avons que le pet représenté par p1_0.
- Si nous souhaitons en plus récupérer le propriétaire en appelant
pet.getOwner().getName()alors une nouvelle requête SQL est exécutée
[Hibernate]
select
p1_0.id,
p1_0.age,
p1_0.name,
p1_0.owner_id,
p1_0.type
from
pets p1_0
where
p1_0.type=?
and p1_0.age<?
and p1_0.owner_id=?
order by
p1_0.name
[Hibernate]
select
o1_0.id,
o1_0.name
from
owners o1_0
where
o1_0.id=?
Ainsi, si nous souhaitons à la fois charger les animaux et le propriétaires en une seule requête nous pouvons utiliser le JOIN FETCH
Long ownerId = 1L; // Replace with the actual owner ID
String jpql = "SELECT p FROM Pet p JOIN FETCH p.owner WHERE p.type = :type AND p.age < :age AND p.owner.id = :ownerId ORDER BY p.name ASC";
TypedQuery<Pet> query = em.createQuery(jpql, Pet.class);
query.setParameter("type", "dog");
query.setParameter("age", 5);
query.setParameter("ownerId", ownerId);
List<Pet> filteredPets = query.getResultList();
// Display results
filteredPets.forEach(System.out::println);
filteredPets.forEach(pet -> {
System.out.println(pet.getOwner().getName());
});Résultat
Lors du pet.getOwner().getName() le propriétaire est déjà chargé
[Hibernate]
select
p1_0.id,
p1_0.age,
p1_0.name,
p1_0.owner_id,
o1_0.id,
o1_0.name,
p1_0.type
from
pets p1_0
join
owners o1_0
on o1_0.id = p1_0.owner_id
where
p1_0.type = ?
and p1_0.age < ?
and p1_0.owner_id = ?
order by
p1_0.name
Conclusion
L’API criteria permet de remplacer les mot-clés JPQL (SELECT, FROM, WHERE) par des équivalent select(), from(), where() …