Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 56

Développement d'une application web CRUD complète

Recommended resources

Bonjour ! Dans la leçon précédente, nous avons exploré la théorie et la mise en place de la connexion entre les Servlets (contrôleur), les JSP (vue) et la couche DAO (modèle). Vous avez appris comment initialiser et rendre accessible le DAO depuis une servlet.

Aujourd'hui, nous passons à l'action. Cette leçon est un projet de synthèse qui vous guidera dans la construction de votre première application web Java complète, de A à Z. L'objectif est de mettre en œuvre les quatre opérations fondamentales de la gestion de données — Create, Read, Update, Delete (CRUD) — pour une entité "Produit". Ce projet consolidera tout ce que vous avez appris sur JDBC, le pattern DAO, les Servlets et les JSP. C'est le point d'orgue de notre étude de l'écosystème Java EE "classique", avant d'aborder des frameworks plus modernes comme Spring Boot.

1. Architecture et Préparation du Projet

Avant d'écrire la moindre ligne de code, ayons une vision claire de l'architecture et de la structure de notre projet. Nous allons suivre scrupuleusement le modèle MVC, en y intégrant notre couche DAO.

Rappel de notre architecture cible : Le Contrôleur (Servlet) orchestre le dialogue entre la requête du client, le Modèle (nos JavaBeans et DAO) et la Vue (JSP).

Structure des Fichiers

Pour une application bien organisée, la structure des répertoires est primordiale. Elle reflète la séparation des responsabilités.

Cette image montre une structure de projet idéale pour notre application CRUD. Nous allons créer des packages distincts pour le modèle (`com.myapp.model`), le DAO (`com.myapp.dao`) et les contrôleurs (`com.myapp.web`).

Plan d'action initial :

  1. Créez un nouveau projet Maven (war).
  2. Ajoutez les dépendances nécessaires dans votre pom.xml : jakarta.servlet-api, mysql-connector-java, et jstl-api et son implémentation.
  3. Base de données : Créez une table produits dans votre base de données MySQL avec les colonnes : id (INT, PRIMARY KEY, AUTO_INCREMENT), nom (VARCHAR), description (TEXT), prix (DECIMAL).
  4. Le Modèle : Créez le bean Produit.java dans le package com.myapp.model avec les attributs correspondants (id, nom, description, prix) ainsi que les getters et setters.
  5. Le DAO : Mettez en place la couche DAO comme vu dans les leçons précédentes :
    • L'interface ProduitDAO.java avec les signatures des méthodes : creer(Produit p), trouverParId(int id), lister(), modifier(Produit p), supprimer(int id).
    • La classe d'implémentation ProduitDAOImpl.java qui implémente cette interface.
    • La DAOFactory et son ServletContextListener pour l'initialisation au démarrage de l'application. Vous pouvez réutiliser et adapter le code du tutoriel de Zeste de Savoir (ressource LINK).

Pour ce projet, nous allons utiliser une seule servlet, ProduitServlet, qui agira comme un contrôleur frontal pour toutes les opérations CRUD sur les produits. Nous la "routerons" en interne à l'aide d'un paramètre d'URL action.


2. Opération "Read" : Afficher la liste des produits

Commençons par la fonctionnalité la plus simple : afficher tous les produits de la base de données.

a. Implémentation du DAO (lister())

Dans votre ProduitDAOImpl, concentrez-vous sur la méthode lister(). Elle doit :

  1. Obtenir une connexion via la DAOFactory.
  2. Créer et exécuter une requête SELECT * FROM produits.
  3. Parcourir le ResultSet.
  4. Pour chaque ligne, créer un objet Produit (en utilisant une méthode de mapping comme vu dans la ressource LINK, section 5).
  5. Ajouter chaque objet Produit à une List<Produit>.
  6. Retourner la liste.
  7. Gérer la fermeture des ressources (ResultSet, Statement, Connection) dans un bloc finally.

b. Le Contrôleur (ProduitServlet)

Créez ProduitServlet.java dans le package com.myapp.web.
Dans la méthode doGet(), nous allons lire le paramètre action pour décider quoi faire.

// ProduitServlet.java

@WebServlet("/produits")
public class ProduitServlet extends HttpServlet {
    private ProduitDAO produitDao;

    public void init() throws ServletException {
        DAOFactory daoFactory = (DAOFactory) getServletContext().getAttribute("daoFactory");
        this.produitDao = daoFactory.getProduitDao(); // Assurez-vous d'avoir cette méthode dans votre factory
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        String action = request.getParameter("action");
        if (action == null) {
            action = "list"; // Action par défaut
        }

        switch (action) {
            case "list":
                listerProduits(request, response);
                break;
            // D'autres cas pour "new", "edit", "delete" viendront ici...
            default:
                listerProduits(request, response);
                break;
        }
    }

    private void listerProduits(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        List<Produit> listeProduits = produitDao.lister();
        request.setAttribute("listeProduits", listeProduits);
        RequestDispatcher dispatcher = request.getRequestDispatcher("/WEB-INF/views/produit-list.jsp");
        dispatcher.forward(request, response);
    }
    
    // La méthode doPost viendra plus tard
}

Note : Placer les JSP dans /WEB-INF/ est une bonne pratique de sécurité pour empêcher l'accès direct via l'URL.

c. La Vue (produit-list.jsp)

Créez produit-list.jsp dans /WEB-INF/views/. Cette page utilise la JSTL pour afficher les données.

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<html>
<head>
    <title>Liste des Produits</title>
</head>
<body>
    <h1>Liste des Produits</h1>
    <p><a href="produits?action=new">Ajouter un produit</a></p>
    <table border="1">
        <tr>
            <th>ID</th>
            <th>Nom</th>
            <th>Prix</th>
            <th>Actions</th>
        </tr>
        <c:forEach var="produit" items="${listeProduits}">
            <tr>
                <td><c:out value="${produit.id}" /></td>
                <td><c:out value="${produit.nom}" /></td>
                <td><c:out value="${produit.prix}" /> €</td>
                <td>
                    <a href="produits?action=edit&id=${produit.id}">Modifier</a>
                    &nbsp;&nbsp;&nbsp;
                    <a href="produits?action=delete&id=${produit.id}" onclick="return confirm('Êtes-vous sûr de vouloir supprimer ce produit ?')">Supprimer</a>
                </td>
            </tr>
        </c:forEach>
    </table>
</body>
</html>

À ce stade, en accédant à http://localhost:8080/votre-app/produits, vous devriez voir la liste des produits de votre base de données.


3. Opération "Create" : Ajouter un produit

L'ajout d'un produit se fait en deux temps : afficher le formulaire, puis le traiter.

a. Afficher le formulaire

  1. Contrôleur : Dans ProduitServlet, ajoutez un nouveau cas dans le switch de doGet() :
    // Dans doGet() de ProduitServlet
    case "new":
        afficherFormulaireCreation(request, response);
        break;
    
    // Nouvelle méthode dans ProduitServlet
    private void afficherFormulaireCreation(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        RequestDispatcher dispatcher = request.getRequestDispatcher("/WEB-INF/views/produit-form.jsp");
        dispatcher.forward(request, response);
    }
    
  2. Vue : Créez le fichier produit-form.jsp. Pour l'instant, c'est un simple formulaire HTML qui pointe vers notre servlet.
    <!-- produit-form.jsp -->
    <form action="produits?action=insert" method="post">
        <h2>Ajouter un Produit</h2>
        <label>Nom:</label>
        <input type="text" name="nom" required /> <br/>
        <label>Description:</label>
        <textarea name="description" required></textarea> <br/>
        <label>Prix:</label>
        <input type="text" name="prix" required /> <br/>
        <button type="submit">Enregistrer</button>
    </form>
    

b. Traiter la soumission

Le traitement se fait dans la méthode doPost() de la servlet. La vidéo ci-dessous montre bien ce mécanisme de récupération des données d'un formulaire.

Développez des sites web avec Java EE: Les formulaires avec JAVA EE

Cette vidéo montre comment traiter un formulaire simple. Concentrez-vous sur la manière dont les données sont récupérées dans la méthode doPost et comment elles sont utilisées.

Regardez comment la servlet récupère les paramètres d'un formulaire soumis en POST après la soumission. C'est exactement ce que nous allons faire.

  1. DAO : Implémentez la méthode creer(Produit p) dans ProduitDAOImpl. Elle utilisera une requête INSERT avec un PreparedStatement.
  2. Contrôleur : Implémentez doPost() et une méthode privée pour l'insertion.
    // Dans ProduitServlet
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        String action = request.getParameter("action");
        if (action == null) {
            action = "list";
        }
        
        switch (action) {
            case "insert":
                insererProduit(request, response);
                break;
            // Le cas "update" viendra ici
            default:
                listerProduits(request, response);
                break;
        }
    }
    
    private void insererProduit(HttpServletRequest request, HttpServletResponse response)
            throws IOException {
        String nom = request.getParameter("nom");
        String description = request.getParameter("description");
        double prix = Double.parseDouble(request.getParameter("prix"));
    
        Produit nouveauProduit = new Produit();
        nouveauProduit.setNom(nom);
        nouveauProduit.setDescription(description);
        nouveauProduit.setPrix(prix);
        
        produitDao.creer(nouveauProduit);
        
        // Redirection pour éviter la re-soumission du formulaire (Pattern Post-Redirect-Get)
        response.sendRedirect("produits");
    }
    

4. Opération "Update" : Modifier un produit

La mise à jour est un mélange entre Read (pour pré-remplir le formulaire) et Create (pour traiter la soumission).

  1. DAO : Implémentez trouverParId(int id) et modifier(Produit p).
  2. Contrôleur (doGet) : Gérez l'action edit.
    // Dans le switch de doGet()
    case "edit":
        afficherFormulaireModification(request, response);
        break;
    
    // Nouvelle méthode dans ProduitServlet
    private void afficherFormulaireModification(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        int id = Integer.parseInt(request.getParameter("id"));
        Produit produitExistant = produitDao.trouverParId(id);
        request.setAttribute("produit", produitExistant);
        RequestDispatcher dispatcher = request.getRequestDispatcher("/WEB-INF/views/produit-form.jsp");
        dispatcher.forward(request, response);
    }
    
  3. Vue (produit-form.jsp) : Modifiez le formulaire pour qu'il puisse gérer à la fois la création et la modification.
    <!-- produit-form.jsp (version améliorée) -->
    <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
    
    <c:if test="${produit != null}">
        <form action="produits?action=update" method="post">
        <input type="hidden" name="id" value="<c:out value='${produit.id}' />" />
    </c:if>
    <c:if test="${produit == null}">
        <form action="produits?action=insert" method="post">
    </c:if>
    
        <h2>
            <c:if test="${produit != null}">Modifier le Produit</c:if>
            <c:if test="${produit == null}">Ajouter un Produit</c:if>
        </h2>
        
        <label>Nom:</label>
        <input type="text" name="nom" value="<c:out value='${produit.nom}' />" required /> <br/>
        
        <label>Description:</label>
        <textarea name="description" required><c:out value='${produit.description}' /></textarea> <br/>
        
        <label>Prix:</label>
        <input type="text" name="prix" value="<c:out value='${produit.prix}' />" required /> <br/>
        
        <button type="submit">Enregistrer</button>
    </form>
    
    L'EL (${produit.nom}) et la JSTL (<c:if>) permettent d'adapter dynamiquement la vue.
  4. Contrôleur (doPost) : Gérez l'action update.
    // Dans le switch de doPost()
    case "update":
        modifierProduit(request, response);
        break;
    
    // Nouvelle méthode dans ProduitServlet
    private void modifierProduit(HttpServletRequest request, HttpServletResponse response)
            throws IOException {
        int id = Integer.parseInt(request.getParameter("id"));
        String nom = request.getParameter("nom");
        String description = request.getParameter("description");
        double prix = Double.parseDouble(request.getParameter("prix"));
    
        Produit produit = new Produit();
        produit.setId(id);
        produit.setNom(nom);
        produit.setDescription(description);
        produit.setPrix(prix);
        
        produitDao.modifier(produit);
        response.sendRedirect("produits");
    }
    

5. Opération "Delete" : Supprimer un produit

C'est l'opération la plus directe.

  1. DAO : Implémentez la méthode supprimer(int id) avec une requête DELETE.
  2. Contrôleur (doGet) : Gérez l'action delete.
    // Dans le switch de doGet()
    case "delete":
        supprimerProduit(request, response);
        break;
    
    // Nouvelle méthode dans ProduitServlet
    private void supprimerProduit(HttpServletRequest request, HttpServletResponse response)
            throws IOException {
        int id = Integer.parseInt(request.getParameter("id"));
        produitDao.supprimer(id);
        response.sendRedirect("produits");
    }
    
    Bonne pratique : Les opérations qui modifient l'état du serveur (comme DELETE) devraient idéalement utiliser la méthode HTTP POST pour des raisons de sécurité et de sémantique. Pour un projet d'apprentissage, l'utilisation de GET est acceptable, mais gardez cette distinction en tête pour des projets professionnels. L'ajout d'une confirmation JavaScript (onclick) est également une précaution simple et efficace.

Conclusion

Félicitations ! Vous venez de construire une application web Java complète et fonctionnelle qui respecte les standards de l'architecture MVC. Vous avez mis en pratique l'ensemble des technologies vues jusqu'à présent pour créer une application réaliste.

Points clés à retenir :

  • Une servlet unique peut servir de contrôleur pour toutes les actions CRUD liées à une entité, en utilisant un paramètre action pour le routage interne.
  • Le pattern Post-Redirect-Get (PRG) est essentiel après une opération de création, mise à jour ou suppression pour éviter les problèmes de re-soumission de formulaire.
  • Les JSP, combinées à la JSTL et à l'EL, permettent de créer des vues dynamiques, capables de s'adapter pour afficher des listes, des formulaires vides ou des formulaires pré-remplis.
  • La structure du projet et la séparation claire des couches (Modèle, Vue, Contrôleur, DAO) sont la clé d'une application maintenable et évolutive.

Ce projet conclut notre exploration approfondie de la pile technologique "classique" de Java EE. Dans le prochain module, nous allons découvrir Spring Boot, un framework moderne qui va considérablement simplifier et accélérer le développement de telles applications, notamment en automatisant une grande partie de la configuration et de la plomberie que nous avons dû mettre en place manuellement ici. Vous apprécierez d'autant plus sa puissance après avoir maîtrisé ces fondations.