Skip to main content

Welcome to a Grasp lesson

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

Lesson 49

Maîtriser les Opérations CRUD en DAO

Recommended resources

Bonjour ! Dans la leçon précédente, nous avons jeté les bases solides du pattern DAO en implémentant les opérations de création (creer) et de lecture (lister, trouverParId). Vous avez vu comment cette architecture clarifie le code en séparant la logique métier de l'accès aux données.

Aujourd'hui, nous allons compléter notre travail en ajoutant les pièces manquantes du puzzle CRUD. L'objectif est d'implémenter les opérations de mise à jour (Update) et de suppression (Delete) au sein de notre couche DAO. À la fin de cette leçon, vous disposerez d'un Data Access Object complet et fonctionnel, capable de gérer l'intégralité du cycle de vie d'un objet en base de données.

1. Implémentation de l'opération de mise à jour (Update)

L'opération de mise à jour est essentielle pour modifier des informations existantes. Par exemple, permettre à un utilisateur de changer son mot de passe.

Étape 1 : Mettre à jour l'interface UtilisateurDao

Comme toujours, nous commençons par le contrat. Nous devons déclarer la nouvelle méthode dans notre interface UtilisateurDao.

Ajoutez la signature de la méthode mettreAJour à votre fichier UtilisateurDao.java :

// src/main/java/com/example/dao/UtilisateurDao.java
package com.example.dao;

import com.example.model.Utilisateur;
import java.util.List;
import java.util.Optional;

public interface UtilisateurDao {
    void creer(Utilisateur utilisateur);
    Optional<Utilisateur> trouverParId(int id);
    List<Utilisateur> lister();
    
    // Nouvelle méthode
    void mettreAJour(Utilisateur utilisateur);

    // void supprimer(int id); // On l'ajoutera juste après
}

Étape 2 : Implémenter la méthode mettreAJour

Maintenant, passons à l'implémentation dans UtilisateurDaoImpl.java. La logique est similaire à celle de creer, mais nous utiliserons une requête SQL UPDATE.

La requête SQL ressemblera à ceci :
UPDATE users SET login = ?, password = ? WHERE id = ?

Voici comment l'implémenter. Ajoutez ce code à votre classe UtilisateurDaoImpl :

// Dans UtilisateurDaoImpl.java

@Override
public void mettreAJour(Utilisateur utilisateur) {
    String sql = "UPDATE users SET login = ?, password = ? WHERE id = ?";
    
    // Le try-with-resources garantit la fermeture du PreparedStatement
    try (PreparedStatement ps = connection.prepareStatement(sql)) {
        
        ps.setString(1, utilisateur.getLogin());
        ps.setString(2, utilisateur.getPassword());
        ps.setInt(3, utilisateur.getId()); // Ne pas oublier le WHERE !
        
        int affectedRows = ps.executeUpdate();
        
        // Optionnel : vérifier si la mise à jour a bien eu lieu
        if (affectedRows == 0) {
            // Dans une vraie application, on pourrait lancer une exception
            System.out.println("La mise à jour a échoué, aucune ligne affectée.");
        }
        
    } catch (SQLException e) {
        // Pour l'instant, on affiche l'erreur. Nous améliorerons ça.
        e.printStackTrace();
    }
}

Points importants :

  • La clause WHERE : C'est la partie la plus critique de la requête UPDATE. Sans elle, vous mettriez à jour toutes les lignes de la table ! L'ID de l'utilisateur est donc indispensable.
  • L'ordre des paramètres : Assurez-vous que l'ordre dans lequel vous appelez ps.set...() correspond aux ? de votre requête SQL.
  • executeUpdate() : Comme pour INSERT et DELETE, cette méthode est utilisée pour les requêtes qui modifient des données. Elle retourne le nombre de lignes qui ont été modifiées. Vérifier cette valeur est une bonne pratique pour s'assurer que l'opération a bien eu l'effet escompté.

2. Implémentation de l'opération de suppression (Delete)

La dernière opération du CRUD est la suppression. Elle permet de retirer une entrée de la base de données.

Étape 1 : Finaliser l'interface UtilisateurDao

Ajoutons la méthode supprimer à notre interface UtilisateurDao.java.

// src/main/java/com/example/dao/UtilisateurDao.java
public interface UtilisateurDao {
    void creer(Utilisateur utilisateur);
    Optional<Utilisateur> trouverParId(int id);
    List<Utilisateur> lister();
    void mettreAJour(Utilisateur utilisateur);
    
    // Nouvelle méthode
    void supprimer(int id);
}

Notre interface est maintenant complète ! Elle définit toutes les opérations CRUD.

Étape 2 : Implémenter la méthode supprimer

La requête SQL est simple : DELETE FROM users WHERE id = ?.

Ajoutez l'implémentation suivante à votre classe UtilisateurDaoImpl :

// Dans UtilisateurDaoImpl.java

@Override
public void supprimer(int id) {
    String sql = "DELETE FROM users WHERE id = ?";
    
    try (PreparedStatement ps = connection.prepareStatement(sql)) {
        
        ps.setInt(1, id);
        
        int affectedRows = ps.executeUpdate();
        
        if (affectedRows == 0) {
            System.out.println("La suppression a échoué, aucune ligne affectée.");
        }
        
    } catch (SQLException e) {
        e.printStackTrace();
    }
}

Ici aussi, la clause WHERE est fondamentale pour ne supprimer que l'enregistrement désiré.


3. Tester le cycle de vie complet (CRUD)

Maintenant que notre DAO est complet, mettons à jour notre méthode main pour tester le cycle de vie entier d'un utilisateur : création, lecture, mise à jour, et suppression.

Remplacez le code de votre méthode main par celui-ci :

// Dans votre classe principale (ex: Main.java)
public static void main(String[] args) {
    String url = "jdbc:mysql://localhost:3306/votre_db";
    String user = "votre_user";
    String password = "votre_password";

    try (Connection connection = DriverManager.getConnection(url, user, password)) {
        UtilisateurDao utilisateurDao = new UtilisateurDaoImpl(connection);

        // --- 1. CREATE ---
        System.out.println("--- 1. Création d'un utilisateur ---");
        Utilisateur nouvelUtilisateur = new Utilisateur(0, "david", "secret123");
        utilisateurDao.creer(nouvelUtilisateur);
        System.out.println("Utilisateur créé : " + nouvelUtilisateur); // L'ID a été mis à jour

        // --- 2. READ ---
        System.out.println("\n--- 2. Lecture de l'utilisateur ---");
        int newId = nouvelUtilisateur.getId();
        Optional<Utilisateur> userFromDb = utilisateurDao.trouverParId(newId);
        userFromDb.ifPresent(u -> System.out.println("Utilisateur trouvé : " + u));

        // --- 3. UPDATE ---
        System.out.println("\n--- 3. Mise à jour de l'utilisateur ---");
        Utilisateur userToUpdate = userFromDb.get();
        userToUpdate.setLogin("david_updated");
        userToUpdate.setPassword("new_password");
        utilisateurDao.mettreAJour(userToUpdate);
        
        // Re-lecture pour vérification
        Optional<Utilisateur> updatedUser = utilisateurDao.trouverParId(newId);
        updatedUser.ifPresent(u -> System.out.println("Utilisateur mis à jour : " + u));

        // --- 4. DELETE ---
        System.out.println("\n--- 4. Suppression de l'utilisateur ---");
        utilisateurDao.supprimer(newId);
        
        // Tentative de re-lecture pour vérification
        Optional<Utilisateur> deletedUser = utilisateurDao.trouverParId(newId);
        if (deletedUser.isEmpty()) {
            System.out.println("Utilisateur avec l'ID " + newId + " a bien été supprimé.");
        } else {
            System.out.println("Erreur : l'utilisateur n'a pas été supprimé.");
        }

    } catch (SQLException e) {
        e.printStackTrace();
    }
}

Exécutez ce code. Vous devriez voir dans la console le déroulement de chaque étape, confirmant que vos méthodes mettreAJour et supprimer fonctionnent correctement.


4. Une pratique professionnelle : la gestion des exceptions

Actuellement, nous nous contentons de faire e.printStackTrace(). En production, c'est une mauvaise pratique. Le code qui appelle notre DAO (la "logique métier") se retrouve obligé de gérer des SQLException, une exception spécifique à JDBC. Cela brise l'abstraction que nous essayons de construire.

La solution est de créer nos propres exceptions et d'encapsuler les SQLException à l'intérieur.

Le modèle DAO - Créez votre application web avec Java EE

Cet article de Zeste de Savoir explique parfaitement pourquoi et comment créer des exceptions personnalisées pour la couche DAO. C'est une étape cruciale pour rendre votre code plus robuste et découplé.

Lisez la section "Création des exceptions du DAO". Concentrez-vous sur la distinction entre les erreurs de configuration et les erreurs d'exécution, et sur la simplicité des classes d'exception créées.

Inspirés par cette lecture, créons notre propre exception. Pour simplifier, nous n'en ferons qu'une pour l'instant.

Créez une nouvelle classe DAOException.java :

// src/main/java/com/example/dao/DAOException.java
package com.example.dao;

public class DAOException extends RuntimeException {
    public DAOException(String message) {
        super(message);
    }

    public DAOException(String message, Throwable cause) {
        super(message, cause);
    }
    
    // Constructeur très utile pour encapsuler une autre exception
    public DAOException(Throwable cause) {
        super(cause);
    }
}

Maintenant, refactorisons nos méthodes dans UtilisateurDaoImpl pour utiliser cette nouvelle exception. Remplacez tous les blocs catch (SQLException e) par :

// Exemple pour la méthode creer()
catch (SQLException e) {
    throw new DAOException("Erreur lors de la création de l'utilisateur", e);
}

// Exemple pour la méthode mettreAJour()
catch (SQLException e) {
    throw new DAOException("Erreur lors de la mise à jour de l'utilisateur", e);
}

// Et ainsi de suite pour toutes les méthodes...

Pourquoi est-ce mieux ?

  1. Abstraction : Le code client n'a plus besoin de connaître SQLException. Il sait juste qu'une DAOException peut survenir.
  2. Type d'exception : En héritant de RuntimeException, notre DAOException devient une exception non vérifiée (unchecked). Cela signifie que le code appelant n'est plus obligé de l'attraper avec un try-catch. C'est un choix de conception courant pour les erreurs d'infrastructure (comme une base de données inaccessible), qui sont souvent irrécupérables à un niveau élevé de l'application.

Conclusion

Félicitations ! Vous disposez maintenant d'une couche d'accès aux données complète, robuste et bien structurée.

Les points clés à retenir :

  • Les opérations UPDATE et DELETE utilisent la méthode executeUpdate() de PreparedStatement, tout comme INSERT.
  • La clause WHERE est absolument cruciale dans ces requêtes pour cibler les bons enregistrements.
  • Un DAO complet implémente les quatre opérations CRUD : Create, Read, Update, Delete.
  • Encapsuler les exceptions spécifiques à la technologie (comme SQLException) dans des exceptions personnalisées (DAOException) est une bonne pratique qui renforce le découplage entre les couches de l'application.

Nous avons maintenant une fondation solide pour la persistance des données. Dans le prochain module, nous allons enfin quitter la console pour entrer dans le monde du web ! Nous verrons comment une application web Java fonctionne, quel est le rôle d'un conteneur de servlets comme Tomcat, et comment nous pourrons utiliser notre DAO pour afficher et manipuler des données dans un navigateur.