• 10 heures
  • Difficile

Ce cours est visible gratuitement en ligne.

course.header.alt.is_video

course.header.alt.is_certifying

J'ai tout compris !

Mis à jour le 01/03/2022

Exécutez des requêtes personnalisées

Vous voilà arrivé dans le dernier chapitre de cours ! Le projet Carlib Assurances a désormais une couche DAL (Data Access Layer) de qualité. Et les opérations du CRUD sont toutes réalisables. Mais est-ce suffisant ?

Pour répondre, imaginez que Carlib Assurances vous demande une fonctionnalité de recherche sur l’application. Les barres de recherche sont communes aujourd’hui, et Carlib Assurances veut permettre à un utilisateur de chercher une assurance via des mots clés.

L’application doit pouvoir renvoyer le ou les produits correspondant à la recherche de l’utilisateur. On considère que les mots clés saisis sont à comparer avec le nom des produits d’assurance.

Comment faire ?

Certains vont peut-être suggérer d’utiliser la méthode getProducts() de la classe ProductService, puis d’écrire un algorithme qui vérifie le nom de chaque produit pour en extraire ceux qui correspondent au nom saisi dans le formulaire de recherche. Cette façon de faire permettrait d’avoir le bon résultat final.

Serait-ce la méthode la plus performante pour obtenir le résultat attendu ?

Pas vraiment ! Il faut, dans la mesure du possible, déléguer les traitements à la base de données elle-même. Ainsi, il serait beaucoup plus pertinent d’avoir une requête SQL capable de demander les produits qui correspondent au nom en question. Par exemple : ‘SELECT * FROM produits WHERE nom=?’

Et jusqu’à présent, ce que nous avons appris ne nous permet pas de faire un tel traitement avec Spring Data JPA. Nous comprenons donc que les opérations du CRUD, que nous avons traitées ensemble, ne sont pas suffisantes pour couvrir toutes les fonctionnalités à implémenter !

A-t-on alors atteint la limite de Spring Data JPA ?

Non, la bonne nouvelle est que Spring Data JPA offre une solution grâce aux requêtes personnalisées !

Les requêtes personnalisées sont des requêtes qui ne sont pas incluses par défaut dans l’interface CrudRepository. Il en existe plusieurs types :

  • les requêtes dérivées : derived query ;

  • les requêtes JPQL ;

  • les requêtes natives SQL.

 Mais je pensais que Spring Data JPA permettait justement de ne pas faire de SQL !

Dans de très nombreux cas en effet, Spring Data JPA vous évite de faire des requêtes SQL. Cependant, soyez conscient qu’il est impossible pour un framework de couvrir TOUS les cas possibles. Par conséquent, les frameworks ORM maintiennent un mécanisme pour pouvoir exécuter des requêtes SQL quand cela devient nécessaire.

Reprenons le cas de notre fonctionnalité de recherche, découvrons comment faire et laissez-moi vous présenter les différents types de requêtes personnalisées. 

Exécutez des Derived Queries

Spring Data JPA est capable d’exécuter une requête qui est dérivée du nom d’une méthode (derived query, en anglais). Concrètement, en ajoutant une nouvelle méthode dans l’interface qui étend CrudRepository, vous permettez à Spring Data JPA d’exécuter de nouvelles requêtes.

Notez l’exemple suivant dans ProductRepository :

package com.openclassrooms.datalayer.repository;
 
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
 
import com.openclassrooms.datalayer.model.Product;
 
@Repository
public interface ProductRepository extends CrudRepository<Product, Integer> {
 
	public Iterable<Product> findByName(String name);
	
}

 À la ligne 11, j’ai rajouté le prototype d’une nouvelle méthode, public Iterable<Product> findByName(String name);

Le nom de cette méthode findByName n’a pas été construit au hasard :

  • Le mot find correspond à la récupération de données.

  • Le mot By est un mot clé utilisé par Spring Data JPA pour indiquer que la requête aura un paramètre.

  • Le mot Name correspond à l’attribut de l’entité qui est notre critère de recherche.

Cette méthode est une requête dérivée (Derived Query) car nous n’allons pas en écrire l’implémentation. Spring Data JPA est capable de générer le code nécessaire à partir du prototype de la méthode !

Modifions ProductService et DataLayerApplication pour essayer cette requête dérivée.

package com.openclassrooms.datalayer.service;
 
import java.util.Optional;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
 
import com.openclassrooms.datalayer.model.Product;
import com.openclassrooms.datalayer.repository.ProductRepository;
 
@Service
public class ProductService {
 
	@Autowired
	private ProductRepository produitRepository;
	
	public Iterable<Product> getProducts() {
		return productRepository.findAll();
	}
	
	public Optional<Product> getProductById(Integer id) {
		return productRepository.findById(id);
	}	
	
	public Iterable<Product> getProductsByName(String name) {
		return productRepository.findByName(name);
	}
	
	public Produit saveProduct(Product product) {
		return productRepository.save(product);
	}
	
	public void deleteProductById(Integer id) {
		productRepository.deleteById(id);
	}
	
}

Lignes 25 et 26, j’ai ajouté une nouvelle méthode comme nous avons eu l’habitude de le faire pendant ce cours.

package com.openclassrooms.datalayer;
 
import javax.transaction.Transactional;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
 
import com.openclassrooms.datalayer.model.Product;
import com.openclassrooms.datalayer.service.CategoryService;
import com.openclassrooms.datalayer.service.CommentService;
import com.openclassrooms.datalayer.service.ProductService;
 
@SpringBootApplication
public class DataLayerApplication implements CommandLineRunner {
 
	@Autowired
	private CategoryService categoryService;
	
	@Autowired
	private ProductService productService;
	
	@Autowired
	private CommentService commentService;
 
	public static void main(String[] args) {
		SpringApplication.run(DataLayerApplication.class, args);
	}
 
	@Override
	@Transactional
	public void run(String... args) throws Exception {
 
		Iterable<Product> searchResults = productService.getProductsByName("AssuranceTousRisques");
		searchResults.forEach(product -> System.out.println(product.getProductId()));	
		
	}		
}

 Ce code affichera dans la console le nom de l’assurance en question, donc 3.

Les possibilités vont bien au-delà du mot By. La documentation de Spring vous fournit un tableau avec les mots clés utilisables pour construire vos requêtes. Par exemple, vous pouvez associer plusieurs critères avec And ou Or 

public Iterable<Product> findByNameAndCost(String name, Integer cost);

Pensons maintenant à une autre recherche : récupérer tous les produits par le nom d’une catégorie. La difficulté est que l’entité Product possède un attribut de type List<Category> et qu’on ne peut donc pas faire un findByCategory, d’autant qu’on possède uniquement le nom de la catégorie, et pas toute l’entité.

Pour répondre à cette problématique, les requêtes dérivées ont la capacité de traverser les relations ! 

Retrouvez-moi dans le screencast qui suit :

package com.openclassrooms.datalayer.repository;
 
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
 
import com.openclassrooms.datalayer.model.Product;
 
@Repository
public interface ProductRepository extends CrudRepository<Produit, Integer> {
 
	public Iterable<Product> findByName(String name);
	
	public Iterable<Product> findByCategoriesName(String name);
	
}

La ligne 13 définit une nouvelle méthode findByCategoriesName :

  • find pour la récupération de données ;

  • By pour introduire des critères ;

  • Categories car l’attribut au sein de l’entité Product se nomme categories ;

  • Name car au sein de l’entité Category il y a l’attribut name.

Ça y est, tu as fini de nous submerger de nouvelles connaissances ? 

Euh… allez, une dernière ! 

Exécutez des requêtes JPQL et des requêtes natives

La réalité des applications Java est que même les requêtes dérivées peuvent ne pas suffire pour implémenter toutes sortes de fonctionnalités demandées. En réponse à cette problématique, Spring Data permet d’exécuter des requêtes JPQL ou des requêtes SQL natives.

Dans les 2 cas, il faut ajouter une nouvelle méthode dans l’interface Repository, et utiliser l’annotation @Query. Voici un exemple :

package com.openclassrooms.datalayer.repository;
 
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
 
import com.openclassrooms.datalayer.model.Product;
 
@Repository
public interface ProductRepository extends CrudRepository<Product, Integer> {
 
	public Iterable<Product> findByName(String name);
	
	public Iterable<Product> findByCategoriesName(String name);
	
	@Query("FROM Product WHERE name = ?1")
    public Iterable<Product> findByNameJPQL(String name);
	
	@Query(value = "SELECT * FROM produit WHERE cout = :cout", nativeQuery = true)
	public Iterable<Product> findByCostNative(@Param("cout") Integer cost);
 
	
}

La méthode findByNameJPQL utilise l’annotation @Query avec une requête JPQL. On note qu’il n’y a pas de clause SELECT, et ce n’est pas le nom de la table mais le nom de l’entité qui est indiqué après le FROM.

La méthode findByCostNative utilise l’annotation @Query avec une requête SQL native. 3 aspects particuliers :

  1. La requête est dans une propriété value de l’annotation.

  2. La propriété nativeQuery de l’annotation doit être à true.

  3. Le paramètre au sein de la méthode doit être annoté @Param afin que l’association soit faite entre le paramètre et la requête SQL.

Voilà ! Je vous ai montré comment... À vous d’aller plus loin et d’explorer pleinement toutes les possibilités qui se sont ouvertes à vous !

À vous de jouer !

Je n’allais pas vous laisser partir sans que vous vous exerciez ! 

Avant de nous quitter, je vous propose d’implémenter :

  • La recherche des catégories :

    • par le nom de la catégorie ;

    • par le nom d’un produit de la catégorie.

  • La recherche des produits :

    • dont le coût est inférieur à 1 000 €.

  • La recherche des commentaires :

    • dont le contenu contient le mot “déçu”.

Pour la correction, c’est via le repository du cours, branche p3c3.

En résumé

Pour effectuer des requêtes personnalisées, nous pouvons utiliser :

  • Les requêtes dérivées (Derived Query) : à partir du nom de la méthode que vous créez dans l’interface Repository, Spring Data JPA est capable d’exécuter des requêtes avec des critères spécifiques.

  • Les requêtes JPQL : le langage de requêtes JPQL se sert de la structure des entités pour exécuter des requêtes SQL. On transmet ces requêtes JPQL via l’annotation @Query sur une méthode déclarée dans l’interface Repository.

  • Les requêtes SQL (Native Query) : via l’annotation @Query et passant la propriété nativeQuery à true, vous pouvez associer une requête SQL native à une méthode de l’interface Repository. Attention, les paramètres de la méthode qui seront utilisés dans la requête doivent être annotés @Param.

À tous les lecteurs, je vous remercie pour l’attention que vous apportez à ce cours ! Ce fut un vrai plaisir de vous accompagner, et je suis convaincu que vous mènerez à bien vos projets à venir avec Spring Data JPA.

S’il est vrai que vous avez encore de nombreuses choses à découvrir, vous possédez désormais de solides fondations pour ce qui est d’interagir avec une base de données grâce à Spring Data JPA !

Exemple de certificat de réussite
Exemple de certificat de réussite