Aller au contenu principal

6) Persistance avec JDBC

Notre jeu fonctionne en mémoire, mais les personnages disparaissent à chaque relance. Dans cette séance, vous allez sauvegarder les personnages dans une base de données MySQL grâce à JDBC (Java Database Connectivity).

Objectifs de la séance

  • Configurer la dépendance MySQL dans pom.xml (Maven)
  • Créer la table personnages en SQL
  • Écrire une classe PersonnageDAO avec sauvegarder() et chargerTous()
  • Utiliser PreparedStatement pour éviter les injections SQL
  • Gérer les ressources avec try-with-resources

Notions théoriques

Qu'est-ce que JDBC ?

JDBC est l'API standard Java pour communiquer avec une base de données relationnelle (MySQL, PostgreSQL, SQLite...). Elle permet d'exécuter des requêtes SQL depuis du code Java.

Dépendance Maven

Dans pom.xml, ajoutez le connecteur MySQL :

<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.3.0</version>
</dependency>

La table SQL

CREATE TABLE IF NOT EXISTS personnages (
id INT AUTO_INCREMENT PRIMARY KEY,
nom VARCHAR(100) NOT NULL,
points_de_vie INT NOT NULL,
force INT NOT NULL,
type VARCHAR(20) NOT NULL
);

Connexion à la base

String url = "jdbc:mysql://localhost:3306/jeu_rpg";
String user = "root";
String password = "root";

Connection connexion = DriverManager.getConnection(url, user, password);
attention

Ne jamais coder le mot de passe en dur dans un fichier versionné (.java, pom.xml). En pratique, utilisez des variables d'environnement ou un fichier .env exclu de Git. Pour ce TP, nous simplifions.

PreparedStatement — la protection contre les injections SQL

Un PreparedStatement est une requête paramétrée. Les paramètres sont remplacés par ? et insérés de façon sécurisée :

String sql = "INSERT INTO personnages (nom, points_de_vie, force, type) VALUES (?, ?, ?, ?)";
PreparedStatement stmt = connexion.prepareStatement(sql);
stmt.setString(1, personnage.getNom());
stmt.setInt(2, personnage.getPointsDeVie());
stmt.setInt(3, personnage.getForce());
stmt.setString(4, "GUERRIER");
stmt.executeUpdate();
attention

N'utilisez jamais la concaténation de String pour construire une requête SQL ("SELECT * FROM t WHERE nom = '" + nom + "'") : cela ouvre une faille d'injection SQL. Utilisez toujours PreparedStatement.

try-with-resources

Java ferme automatiquement les ressources (Connection, PreparedStatement, ResultSet) grâce au try-with-resources :

try (Connection connexion = DriverManager.getConnection(url, user, password);
PreparedStatement stmt = connexion.prepareStatement(sql)) {

stmt.setString(1, "Aragorn");
stmt.executeUpdate();

} catch (SQLException e) {
System.out.println("Erreur SQL : " + e.getMessage());
}
// connexion et stmt sont fermés automatiquement ici

Le pattern DAO

DAO (Data Access Object) est un patron de conception qui regroupe toutes les opérations de base de données pour un type d'objet dans une seule classe.

public class PersonnageDAO {

private static final String URL = "jdbc:mysql://localhost:3306/jeu_rpg";
private static final String USER = "root";
private static final String PASSWORD = "root";

public void sauvegarder(Personnage personnage) {
// INSERT INTO ...
}

public ArrayList<Personnage> chargerTous() {
// SELECT * FROM ...
}
}

Exemple pratique

public class PersonnageDAO {

private static final String URL = "jdbc:mysql://localhost:3306/jeu_rpg";
private static final String USER = "root";
private static final String PASSWORD = "root";

public void sauvegarder(Personnage personnage) {
String sql = "INSERT INTO personnages (nom, points_de_vie, force, type) VALUES (?, ?, ?, ?)";
String type = (personnage instanceof Guerrier) ? "GUERRIER" : "MAGE";

try (Connection connexion = DriverManager.getConnection(URL, USER, PASSWORD);
PreparedStatement stmt = connexion.prepareStatement(sql)) {

stmt.setString(1, personnage.getNom());
stmt.setInt(2, personnage.getPointsDeVie());
stmt.setInt(3, personnage.getForce());
stmt.setString(4, type);
stmt.executeUpdate();

System.out.println(personnage.getNom() + " sauvegardé en base !");

} catch (SQLException e) {
System.out.println("Erreur lors de la sauvegarde : " + e.getMessage());
}
}

public ArrayList<Personnage> chargerTous() {
ArrayList<Personnage> liste = new ArrayList<>();
String sql = "SELECT nom, points_de_vie, force, type FROM personnages";

try (Connection connexion = DriverManager.getConnection(URL, USER, PASSWORD);
PreparedStatement stmt = connexion.prepareStatement(sql);
ResultSet rs = stmt.executeQuery()) {

while (rs.next()) {
String nom = rs.getString("nom");
int pv = rs.getInt("points_de_vie");
int force = rs.getInt("force");
String type = rs.getString("type");

if (type.equals("GUERRIER")) {
liste.add(new Guerrier(nom, pv, force));
} else {
liste.add(new Mage(nom, pv, force, 50));
}
}

} catch (SQLException e) {
System.out.println("Erreur lors du chargement : " + e.getMessage());
}

return liste;
}
}

Test de mémorisation/compréhension


Que signifie JDBC ?


Pourquoi utiliser PreparedStatement plutôt que Statement ?


Que fait `try-with-resources` automatiquement ?


Quel numéro d'index utilise-t-on pour le premier paramètre ? d'un PreparedStatement ?


Qu'est-ce que le pattern DAO ?


TP pour réfléchir et résoudre des problèmes

Vous allez créer la classe PersonnageDAO complète et l'utiliser pour sauvegarder puis recharger une équipe.

Étape 1 — Créer la table en base

Avant d'écrire du Java, créez la base de données et la table dans MySQL (via MySQL Workbench ou la ligne de commande).


Bonne pratique - CREATE TABLE IF NOT EXISTS

Utilisez toujours IF NOT EXISTS pour éviter une erreur si la table existe déjà. C'est particulièrement utile dans les scripts de déploiement qui peuvent être exécutés plusieurs fois.

Étape 2 — Écrire la méthode sauvegarder()

Implémentez sauvegarder(Personnage personnage) dans PersonnageDAO avec un PreparedStatement et un try-with-resources.


Bonne pratique - try-with-resources pour toutes les ressources JDBC

Une connexion non fermée est une fuite de ressource qui peut épuiser le pool de connexions en production. try-with-resources garantit la fermeture même en cas d'exception, sans écrire de bloc finally.

Étape 3 — Écrire la méthode chargerTous()

Implémentez chargerTous() qui exécute un SELECT et reconstruit les objets Personnage depuis le ResultSet.


Bonne pratique - .equals() pour comparer des String

En Java, == compare les références (adresses mémoire) des objets, pas leur contenu. Pour comparer le texte de deux String, utilisez toujours .equals(). Sinon, vous pouvez obtenir false même pour deux chaînes identiques.

📌 Une solution