• 20 heures
  • Difficile

Ce cours est visible gratuitement en ligne.

Ce cours est en vidéo.

Vous pouvez obtenir un certificat de réussite à l'issue de ce cours.

J'ai tout compris !

Mis à jour le 17/08/2018

Utilisez JPA pour communiquer avec une base de données

Connectez-vous ou inscrivez-vous gratuitement pour bénéficier de toutes les fonctionnalités de ce cours !

Ajoutez une base de données intégrée et générez les tables

Transformez la classe Product en entité

Afin de pouvoir générer nos tables, nous avons besoin de transformer notre classe Product en entité gérée par JPA.

Nous allons commencer par importer toutes les dépendances nécessaires à l'utilisation du JPA. Spring Boot nous offre pour cela un starter prêt à l'emploi : spring-boot-starter-data-jpa. Commençons par l'ajouter dans la balise  <dependencies>  de notre pom.xml, comme illustré ci-dessous :

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>


        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

Afin de nous assurer que les dépendances ont bien été importées, nous pouvons cliquer sur l'icône de rafraichissement du projet Maven fournie par IntelliJ, comme le montre la figure suivante : 

Figure 1
Rafraichissement du projet Maven

Nous pouvons désormais ajouter les annotations  @entity  ,  @Id  et  @GeneratedValue  à la classe Product. Modifiez  donc la classe pour obtenir le code ci-après :

package com.ecommerce.microcommerce.model;

import com.fasterxml.jackson.annotation.JsonFilter;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

@Entity
//@JsonFilter("monFiltreDynamique")
public class Product {

    @Id
    @GeneratedValue
    private int id;

    private String nom;
    private int prix;

    //information que nous ne souhaitons pas exposer
    private int prixAchat;
    ...
    ...
  }

 Explications : 

  • Vous annotez la classe avec @Entity afin qu'elle soit scannée et prise en compte. Il n'y ainsi pas besoin de passer par le traditionnel (et complexe) fichier persistence.xml.

  • Vous annotez l'attribut id avec @Id et @GeneratedValue afin qu'il soit identifié en tant que clé unique auto-générée. 

Ajoutez une base de données

Dans un cas réel, notre Microservice doit communiquer avec une base de données qui lui est dédiée, disponible sur un serveur distant. Pour nos tests, nous allons utiliser une base de données H2 qui est intégrable directement dans notre Microservice. H2 est une base de données très légère (1Mo). Elle va créer les tables et les données uniquement en mémoire vive. Une fois l'application fermée, ces données sont perdues. Cette approche est très utile quand vous développez un Microservice car elle permet de refaire les tests autant de fois que nécessaire en partant d'une base de données propre. L'autre avantage est qu'elle est très simple à mettre en place et complètement auto-configurée par Spring Boot.

Modifiez le fichier pom.xml afin d'ajouter H2, comme illustré ci-après :

<dependencies>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
  </dependency>

  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
  </dependency>

  <dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <scope>runtime</scope>
  </dependency>

  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
  </dependency>
</dependencies>

Actualisez Maven afin d'importer la dépendance. Nous allons maintenant modifier application.properties afin de remplacer les configurations par défaut et demander à Spring d'afficher les requêtes SQL et d'activer l'interface graphique H2, ce qui permettra de visualiser nos tables.

Complétez le fichier application.properties afin d''obtenir le fichier suivant :

server.port 9090

# Configurations H2
spring.jpa.show-sql=true
spring.h2.console.enabled=true

L'entité et la base de données sont désormais prêtes à l'emploi !

Il nous reste une dernière étape : configurer la table Product qui va être créée pour qu'elle contienne des données par défaut. Vous n'allez tout de même pas les rentrer à la main à chaque fois que vous redémarrez votre application ? :p

Créez tout simplement un fichier data.sql dans le dossier resources et écrivez les requêtes SQL nécessaires à l'insertion de vos données. Dans notre cas, nous allons utiliser les mêmes données que nous avions écrites en dur dans le DAO :

INSERT INTO product VALUES(1, 'Ordinateur portable' , 350, 120);
INSERT INTO product VALUES(2, 'Aspirateur Robot' , 500, 200);
INSERT INTO product VALUES(3, 'Table de Ping Pong' , 750, 400);

C'est tout ! Ce fichier sera récupéré automatiquement puis exécuté dans la base de données une fois que la table sera créée.

Redémarrez votre application et rendez-vous sur la console de H2 à l'adresse : http://localhost:9090/h2-console/. Dans le champ JDBC URL, saisissez  jdbc:h2:mem:testdb afin de configurer la connexion vers la base de données testdb située en mémoire vive. 

Figure 2
Console de H2

Cliquez ensuite sur Connect. La console vous propose alors une interface permettant de visualiser la table PRODUCT . Celle-ci a été créée grâce à l'annotation @Entity définie pour la classe Product. Cette table contient comme prévu les données de notre fichier data.sql, comme l'illustre la figure suivante :

Figure 3
Visualisation de la table Product

Utilisez Spring Data JPA

Créez un Repository et utilisez les opérations CRUD auto-générées

Spring Data JPA est un autre framework de Spring qui facilite grandement l'utilisation de JPA. Il va nous permettre de générer toutes sortes d'opérations vers la base de données, sans que nous ayons à écrire la moindre requête, ni même la moindre implémentation DAO. :magicien:  Il nous suffit d'hériter de l'interface JpaRepository.

Rendez-vous dans le fichier ProductDao et modifiez-le comme suit : 

package com.ecommerce.microcommerce.dao;

import com.ecommerce.microcommerce.model.Product;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface ProductDao extends JpaRepository<Product, Integer> {

}

Quand vous héritez de JpaRepository, il faut indiquer comme premier paramètre l'entité concernée, puis le type d'id. Dans ce cas, Product et Integer. C'est tout ! 

Supprimez  la classe ProductDaoImpl qui n'est plus nécessaire.

Dans le contrôleur (ProductController), commentez toutes les méthodes sauf listeProduits afin que l'on puisse les modifier et les tester une à une :

package com.ecommerce.microcommerce.web.controller;

import com.ecommerce.microcommerce.dao.ProductDao;
import com.ecommerce.microcommerce.model.Product;
import com.fasterxml.jackson.databind.ser.FilterProvider;
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.json.MappingJacksonValue;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
import java.net.URI;
import java.util.List;

@RestController
public class ProductController {

    @Autowired
    private ProductDao productDao;

    //Récupérer la liste des produits
    @RequestMapping(value = "/Produits", method = RequestMethod.GET)
    public MappingJacksonValue listeProduits() {
        Iterable<Product> produits = productDao.findAll();

        SimpleBeanPropertyFilter monFiltre = SimpleBeanPropertyFilter.serializeAllExcept("prixAchat");

        FilterProvider listDeNosFiltres = new SimpleFilterProvider().addFilter("monFiltreDynamique", monFiltre);

        MappingJacksonValue produitsFiltres = new MappingJacksonValue(produits);

        produitsFiltres.setFilters(listDeNosFiltres);

        return produitsFiltres;
    }

    //Récupérer un produit par son Id
   /* @GetMapping(value = "/Produits/{id}")
    public Product afficherUnProduit(@PathVariable int id) {
        return productDao.findById(id);
    }

    //ajouter un produit
    @PostMapping(value = "/Produits")
    public ResponseEntity<Void> ajouterProduit(@RequestBody Product product) {
        Product productAdded =  productDao.save(product);
        if (productAdded == null)
            return ResponseEntity.noContent().build();

        URI location = ServletUriComponentsBuilder
                .fromCurrentRequest()
                .path("/{id}")
                .buildAndExpand(productAdded.getId())
                .toUri();
        return ResponseEntity.created(location).build();
    }*/
}

Parmi les opérations qu'offre JpaRepository, nous trouvons findAll(), qui permet de récupérer toutes les données de l'entité concernée. findAll() retourne un Iterable, qu'il nous faut spécifier comme type de retour de la méthode.

Pour vérifier le bon fonctionnement, lancez votre application puis vous rendez-vous sur http://localhost:9090/Produits. Vous pouvez admirer le résultat, sorti tout droit de la base de données. Vous pouvez le vérifier en changeant une valeur dans celle-ci, comme illustré ci-dessous :

Figure 4
Modification d'une valeur dans la base de données

Nous obtenons ainsi :

[
  {
    "id": 1,
    "nom": "Ordinateur portable changé !!",
    "prix": 350
  },
  {
    "id": 2,
    "nom": "Aspirateur Robot",
    "prix": 500
  },
  {
    "id": 3,
    "nom": "Table de Ping Pong",
    "prix": 750
  }
]

Les autres principales opérations auto-générées que nous pouvons utiliser sont :

  • delete(int id) : supprime le produit correspondant à l'id passée en argument 

  • count() : calcule le nombre de produits

  • save(Product produit) : ajoute le produit  passé en argument. Cette méthode peut également recevoir un Iterable<> de produits pour ajouter plusieurs produits.

Vous pouvez consulter la liste complète ici

Utilisez les requêtes générées par le nom de la méthode

La magie de Spring Data va au-delà des requêtes CRUD. Le framework est capable de générer la requête SQL automatiquement en partant du nom de votre méthode !

Prenons un exemple : tout d'abord, ajoutons cette méthode à notre Repository :

@Repository
public interface ProductDao extends JpaRepository<Product, Integer> {

    Product findById(int id);
    
}

Puis retournons dans ProductController et décommentons afficherUnProduit

    //Récupérer un produit par son Id
    @GetMapping(value = "/Produits/{id}")
    public Product afficherUnProduit(@PathVariable int id) {
        return productDao.findById(id);
    }

Une fois l'application redémarrée, nous obtenons le bon résultat sans avoir écrit la moindre requête.

 Mais comment est-ce possible ?

Les conventions ! Spring Data JPA propose un ensemble de conventions qui lui permettront de déduire la bonne requête à partir du nom de la méthode. Vous avez ici l'ensemble des mots clés acceptés.

Prenons comme exemple la récupération des produits dont le prix est supérieur à 400. Commençons par ajouter une méthode findByPrixGreaterThan au Repository, à l'intérieur de la définition de l'interface ProductDAO  :

@Repository
public interface ProductDao extends JpaRepository<Product, Integer> {

    Product findById(int id);

    List<Product> findByPrixGreaterThan(int prixLimit);
}

Toute la logique est fournie par le nom de la méthode !

  • findBy : indique que l'opération à exécuter est un SELECT ;

  • Prix : fournit le nom de la propriété sur laquelle le SELECT s'applique

  • GreaterThan : définit une condition "plus grand que" 

La valeur à appliquer à la condition est, quant à elle, définie par le paramètre prixLimit 

Cette méthode génère une requête équivalent au pseudo-code SQL suivant :

select * from product  where prix > [un chiffre ici]

Afin de mettre cette méthode en action, nous allons créer une méthode testeDeRequetes dans notre contrôleur :  

@GetMapping(value = "test/produits/{prixLimit}")
public List<Product> testeDeRequetes(@PathVariable int prixLimit) {
    return productDao.findByPrixGreaterThan(400);
}

Rendez-vous à l'adresse http://localhost:9090/test/produits/300 . Vous pouvons constater que seuls les produits dont le prix est supérieur à 300 sont retournés.

Si vous souhaitez vérifier la requête générée, par curiosité ou afin de débugger, rendez-vous dans la console. Le fait d'avoir ajouté précédemment  spring.jpa.show-sql=true  à notre fichier application.properties permet d'afficher l'ensemble des requêtes, comme illustré ci-après :

Hibernate : select product0_.id as id1_0_, product0_.nom as nom2_0_, product0_.prix as prix3_0_, product0_.prix_achat as prix_ach4_0_ from product product0_ where product0_.prix>?

Prenons un dernier exemple avec la méthode findByNomLike(). Pour cela, ajoutez le code suivant à ProductDao :

List<Product> findByNomLike(String recherche);

La  requête générée automatiquement va retourner les produits dont le nom ressemble à la chaine de caractères passée comme argument. Pour tester, écrivez le code suivant dans ProductController :

@GetMapping(value = "test/produits/{recherche}")
    public List<Product> testeDeRequetes(@PathVariable String recherche) {
        return productDao.findByNomLike("%"+recherche+"%");
    }

Accédez enfin à l'adresse http://localhost:9090/test/produits/Ordinateur.

Implémentez POST, DELETE et PUT

POST

Pour gérer l'ajout de produit, il nous suffit de décommenter la méthode ajouterProduit. Comme save fait partie des requêtes CRUD auto-générée par Spring Data, vous n'avez absolument rien à faire de plus. 

Démarrez votre application et rendez vous dans Postman. Récupérez dans l'historique la requête POST que nous avons utilisée dans la partie précédente du cours et exécutez-la :

Figure 5
Execution de la requête dans Postman

En bas à droite, vous voyez le code 201 vous indiquant que le produit a été créée. Rendez-vous dans la base de données pour vérifier qu'il ait bien été ajouté :

Figure 66
Contenu de la table Produit après exécution du POST

Aussi simple que ça :)

DELETE

Passons à DELETE. À nouveau, il n'y a rien à faire, à part créer la méthode dans notre contrôleur :

@DeleteMapping (value = "/Produits/{id}")
   public void supprimerProduit(@PathVariable int id) {

       productDao.delete(id);
   }

Cette fois, dans Postman, veillez à bien choisir DELETE comme type de requête, comme illustré ci-dessous :

Figure 7
Requête DELETE dans Postman

Vous pouvez ensuite vérifier que le produit 1 a bien été supprimé.

PUT

Pour mettre à jour, la méthode est exactement comme pour POST, excepté que le produit passé en paramètre a le même Id qu'un produit existant :

@PutMapping (value = "/Produits")
  public void updateProduit(@RequestBody Product product) {

      productDao.save(product);
  }

Dans Postman, vous avez :

Figure 8

Voilà, votre produit avec l'id "1" a été mis à jour dans la base de données.

Écrivez des requêtes à la main

Soyons franc, il est parfois nécessaire d'exécuter des requêtes plus complexes qui ne peuvent pas être générées par ce système de conventions. Pragmatique, Spring nous permet d'écrire une bonne requête artisanale à l'ancienne !

Pour cela, nous allons utiliser la notation JPA (JPQL). En effet, il est hors de question d'utiliser un langage spécifique à une base de données puisque notre Microservice ne doit être couplé à aucun type de base de données.

L'annotation qui permet de le faire est @Query . Regardons un exemple :

@Query("SELECT id, nom, prix FROM Product p WHERE p.prix > :prixLimit")
   List<Product>  chercherUnProduitCher(@Param("prixLimit") int prix);

En paramètre de l'annotation, vous placez tout simplement votre requête JPQL. L'annotation @Param permet de spécifier le nom du paramètre que vous allez recevoir. Cette annotation est optionnelle. Si elle n'est pas utilisée, l'ordre dans lequel les arguments sont fournis est utilisé. Vous aurez alors une requête de type : 

SELECT id, nom, prix FROM Product p WHERE p.prix > ?1
Exemple de certificat de réussite
Exemple de certificat de réussite