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
personnagesen SQL - Écrire une classe
PersonnageDAOavecsauvegarder()etchargerTous() - Utiliser
PreparedStatementpour é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);
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();
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
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).
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.
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.
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.