Application Desktop Java

Gestion du personnel des ligues sportives — Architecture POO, JDBC, Patron Passerelle.

Java 1.8 JDBC / MySQL Maven JUnit 5 POO
← Retour au portfolio

Contexte du projet

Projet d'atelier professionnel réalisé en 2ème année de BTS SIO SLAM. L'objectif était de concevoir une application desktop en Java permettant à la M2L (Maison des Ligues) de gérer les ligues sportives qu'elle héberge ainsi que leurs employés, via une interface entièrement en ligne de commande. Les données sont persistées dans une base MySQL via JDBC.

Fonctionnalités principales

Architecture du projet

Flux d'exécution

ConsolecommandLine
Métierpersonnel
Passerelleinterface
JDBC / Sérial.persistance

Organisation des packages

PackageRôle
personnelCouche métier : Employe, Ligue, GestionPersonnel, exceptions
commandLineInterface console : PersonnelConsole, LigueConsole, EmployeConsole
jdbcPersistance MySQL : connexion, CRUD complet via PreparedStatement
serialisationPersistance alternative : sérialisation Java binaire
testsUnitairesTests JUnit 5 : testLigue, testEmploye

Patron de conception : Passerelle (Gateway)

L'interface Passerelle découple totalement la logique métier de la persistance. Basculer entre MySQL et sérialisation ne nécessite qu'un seul changement de constante :

💻 Voir le code — Sélection de la passerelle

GestionPersonnel.java

public final static int SERIALIZATION = 1, JDBC = 2,
        TYPE_PASSERELLE = JDBC; // ← changer ici pour switcher

private static Passerelle passerelle =
        TYPE_PASSERELLE == JDBC
        ? new jdbc.JDBC()
        : new serialisation.Serialization();

Modèle de données

Deux tables reliées par une clé étrangère nullable (id_ligue NULL = employé root sans ligue).

LIGUE
id_ligueINTPK AUTO_INCREMENT
nom_ligueVARCHAR(100)NOT NULL
EMPLOYE
id_employeINTPK AUTO_INCREMENT
nom_employeVARCHAR(100)
prenom_employeVARCHAR(100)
mail_employeVARCHAR(150)
password_employeVARCHAR(255)
date_arrivee_employeDATENULLABLE
date_départ_employeVARCHARNULLABLE
id_ligueINTFK → LIGUE (nullable)
rôle_employeVARCHAR(20)'admin' | 'employe'

0,n ── Appartient ── 0,1  |  id_ligue NULL = root (aucune ligue)

Description des classes métier

🏗️ GestionPersonnel — Singleton central

Orchestre toutes les opérations sur ligues et employés. Une seule instance possible.

MéthodeRetourDescription
getGestionPersonnel()GestionPersonnelRetourne l'unique instance (Singleton)
addLigue(nom)LigueCrée et persiste une ligue
getLigues()SortedSet<Ligue>Toutes les ligues en lecture seule
getRoot()EmployeRetourne le super-utilisateur root
sauvegarder()voidDélègue la sauvegarde à la passerelle
delete(Employe)voidSuppression via la passerelle active
🏆 Ligue — Entité sportive

Contient un ensemble trié d'employés. Le root est administrateur par défaut jusqu'à affectation explicite.

MéthodeRetourDescription
getNom() / setNom()String / voidLecture/écriture du nom, persisté en BDD
getEmployes()SortedSet<Employe>Liste triée alphabétiquement, lecture seule
addEmploye(...)EmployeSeul moyen de créer un employé
setAdministrateur(emp)voidChange l'admin + met à jour les rôles en BDD
setAdministrateurFromJDBC()voidAffecte l'admin au chargement sans appel BDD
remove()voidRetire la ligue de GestionPersonnel
👤 Employe — Membre d'une ligue

Instanciable uniquement via Ligue.addEmploye(). Gère la validation des dates et la protection du root.

MéthodeRetourDescription
setDateArrivee(date)voidLève DateInvalide si date > aujourd'hui
setDateDepart(date)voidLève DateInvalide si date < dateArrivée
estAdmin(ligue)booleanVrai si this est l'admin de la ligue donnée
estRoot()booleanVrai si this est le root
checkPassword(pwd)booleanCompare le mot de passe fourni
remove()voidSuppression ; lève ImpossibleDeSupprimerRoot si root

Employe.java — Validation des dates

public void setDateArrivee(LocalDate dateArrivee) throws DateInvalide {
    if (dateArrivee != null && dateArrivee.isAfter(LocalDate.now()))
        throw new DateInvalide("La date d'arrivée ne peut pas être future.");
    this.dateArrivee = dateArrivee;
}

public void setDateDepart(LocalDate dateDepart) throws DateInvalide {
    if (dateDepart != null && dateDepart.isBefore(dateArrivee))
        throw new DateInvalide("La date de départ ne peut pas précéder l'arrivée.");
    this.dateDepart = dateDepart;
}
🔌 Interface Passerelle — Contrat CRUD

Passerelle.java

public interface Passerelle {
    GestionPersonnel getGestionPersonnel();
    void sauvegarderGestionPersonnel(GestionPersonnel gp) throws SauvegardeImpossible;
    int insert(Ligue ligue)    throws SauvegardeImpossible;
    int insert(Employe employe) throws SauvegardeImpossible;
    int update(Ligue ligue)    throws SauvegardeImpossible;
    int update(Employe employe) throws SauvegardeImpossible;
    int delete(Employe employe) throws SauvegardeImpossible;
}

Couche JDBC

La classe JDBC implémente Passerelle et gère toutes les interactions avec MySQL via des PreparedStatement. La connexion est configurée dans Credentials.java (non versionné).

Chargement en 3 phases (getGestionPersonnel)

1️⃣
Ligues
SELECT * FROM LIGUE
2️⃣
Employés
WHERE id_ligue = ?
3️⃣
Root
WHERE id_ligue IS NULL
💻 Voir le code — insert(Employe)

JDBC.java — insert(Employe)

public int insert(Employe employe) throws SauvegardeImpossible {
    PreparedStatement ps = connection.prepareStatement(
        "INSERT INTO EMPLOYE (nom_employe, prenom_employe, mail_employe,"
      + " password_employe, date_arrivee_employe, date_départ_employe,"
      + " id_ligue, rôle_employe) VALUES(?,?,?,?,?,?,?,?)",
        Statement.RETURN_GENERATED_KEYS);

    ps.setString(1, employe.getNom());
    ps.setString(2, employe.getPrenom());
    ps.setObject(5, employe.getDateArrivee());
    ps.setString(6, employe.getDateDepart() != null
        ? employe.getDateDepart().toString() : null);

    if (employe.getLigue() != null)
        ps.setInt(7, employe.getLigue().getId());
    else
        ps.setNull(7, java.sql.Types.INTEGER); // root = pas de ligue

    ps.setString(8, employe.estAdmin(employe.getLigue()) ? "admin" : "employe");
    ps.executeUpdate();
    ResultSet id = ps.getGeneratedKeys();
    id.next();
    return id.getInt(1); // id auto-généré renvoyé à l'objet Java
}
✏️ Voir le code — update(Employe)

JDBC.java — update(Employe)

public int update(Employe employe) throws SauvegardeImpossible {
    PreparedStatement ps = connection.prepareStatement(
        "UPDATE EMPLOYE SET nom_employe=?, prenom_employe=?, mail_employe=?,"
      + " password_employe=?, date_arrivee_employe=?, date_départ_employe=?,"
      + " id_ligue=?, rôle_employe=? WHERE id_employe=?");

    ps.setObject(5, employe.getDateArrivee());
    ps.setString(6, employe.getDateDepart() != null
        ? employe.getDateDepart().toString() : null);

    // Rôle recalculé à chaque update (gère le changement d'admin)
    ps.setString(8, (employe.getLigue() != null
        && employe.estAdmin(employe.getLigue())) ? "admin" : "employe");
    ps.setInt(9, employe.getId());
    return ps.executeUpdate();
}

Tests unitaires JUnit 5

Deux classes de test couvrent les entités métier avec des cas nominaux et des cas d'erreur.

testLigue.java

Création d'une ligue avec le bon nom
Ajout d'un employé dans la liste triée
DateInvalide si date arrivée future
DateInvalide si départ avant arrivée
Renommage de ligue (get/set)
Changement et lecture de l'administrateur
Suppression d'une ligue
Suppression de l'admin → absent de la liste

testEmploye.java

Getter / setter nom
Getter / setter prénom
Getter / setter mail
checkPassword() après setPassword()
getLigue() retourne la bonne ligue
Getter / setter date d'arrivée
Getter / setter date de départ
Suppression d'un employé
💻 Voir un exemple — ExceptionDateInvalideDA

testLigue.java

@Test
void ExceptionDateInvalideDA() throws SauvegardeImpossible, DateInvalide {
    Ligue ligue = gestionPersonnel.addLigue("Fléchettes");
    LocalDate dateArrivee = LocalDate.now().plusDays(1); // date future

    assertThrows(DateInvalide.class, () -> {
        ligue.addEmploye("Laporte", "Jean", "[email protected]", "pass",
                         dateArrivee, null); // doit lever DateInvalide
    });
}

Bilan Technique

Ce projet m'a permis de maîtriser la chaîne complète d'un applicatif Java desktop :

⚠️ Points d'amélioration identifiés : Les mots de passe sont stockés en clair — l'utilisation de BCrypt serait nécessaire en production. La suppression d'une ligue ne cascade pas encore en BDD (ON DELETE CASCADE à ajouter).
📄 Télécharger la Documentation Technique complète 💻 Code Source GitHub 📄 Fiche de Situation
s