Spring & JDBC 5 : DAO et CRUD
Avec les quêtes précédentes, tu as eu l'occasion de faire les principales opérations possibles sur une base de données.
Quand on regroupe tous ces opérations, on appelle cela un CRUD :
- Create
- Read
- Update
- Delete
Tu vas apprendre à mettre en place toutes ces méthodes dans un repository et à faire de l'abstraction en mettant en place une interface.
Objectifs
- Comprendre la notion de DAO
- Créer une interface contenant les signatures des méthodes du CRUD
- Implémenter un CRUD
Création d'un DAO
Quelle que soit le système de base de données sur lequel tu pourras interagir, les actions resteront les mêmes :
-
tu pourras créer des éléments (Create)
-
tu pourras lire des éléments (Read)
-
tu pourras mettre à jour des éléments (Update)
-
tu pourras supprimer des éléments (Delete)
Finalement, peu importe que ta base de données soit sur une base MySQL, MongoDB ou même simplement un mock utilisant des ArrayList : les méthodes resteront identiques.
Dans ces cas-là, il est possible de mettre en place une abstraction grâce à une interface. On parle alors d'un objet d'accès aux données, ou DAO (Data Access Object) : c'est un design pattern permettant de transformer des données en instance d’un objet (on parle alors de mapping).
Tu trouveras plus d'information sur le design pattern DAO dans les ressouces.
Dans notre cas, le CRUD sera modélisé par le DAO suivant :
public interface CrudDao {
// Create
Object save(Object entity);
// Read one
Object findById(Long id);
// Read all
List<Object> findAll();
// Update
Object update(Object entity);
// Delete
void deleteById(Long id);
}
Cependant, si tu veux pouvoir utiliser cette interface quel que soit le type d'objet à stocker dans ta base de données, nous avons un problème : les signatures de plusieurs méthodes contiennent le type Object.
Si tu implémentes l'interface comme ça, tu devras systématiquement transtyper (caster) les références récupérées via ton DAO vers les types que tu souhaites (vers Wizard par exemple).
Tu veux éviter ça ? Pas de problèmes : tu vas ajouter une nouvelle corde à ton arc Java : les types génériques !
Un type générique est représenté par une lettre (souvent T) : lors de l'appel de la méthode, il suffira de lui préciser le type attendu... et voilà !
Tu verras en détail les types génériques plus tard, en attendant tu peux lire le lien en ressource afin d'en apprendre plus.
Voici à quoi ressemble l'interface, avec l'utilisation d'un type générique T :
public interface CrudDao<T> {
T save(T entity);
T findById(Long id);
List<T> findAll();
T update(T entity);
void deleteById(Long id);
}
Tu remarques qu'après le nom de l'interface, il y a <T> : lorsque l'interface sera implémentée, il faudra lui spécifier le type. Par exemple si l'on crée un repository de Wizard :
public class WizardRepository implements CrudDao<Wizard> {
// here you'll need to implement methods from CrudDao
}
La documentation officielle sur les types génériques
Lire la section "II. Le pattern DAO : Qu'est-ce que c'est ?"
Implémentation d'un CRUD
Voici à quoi ressemble le repository une fois l'interface implémentée :
public class WizardRepository implements CrudDao<Wizard> {
@Override
public Wizard save(Wizard wizard) {
// TODO Create
return null;
}
@Override
public Wizard findById(Long id) {
// TODO Read one
return null;
}
@Override
public List<Wizard> findAll() {
// TODO Read all
return null;
}
@Override
public Wizard update(Wizard wizard) {
// TODO Update
return null;
}
@Override
public void deleteById(Long id) {
// TODO Delete
}
}
Tu as déjà vu dans les quêtes précédentes comment implémenter ces méthodes, ou presque. En effet, les méthodes save et update s'attendent désormais à un objet en paramètre, au lieu d'un l'ensemble des données à stocker.
La différence n'est pas énorme : il suffira d'aller chercher ces éléments avec leurs accesseurs respectifs :
@Override
public Wizard save(Wizard wizard) {
try {
Connection connection = DriverManager.getConnection(
DB_URL, DB_USER, DB_PASSWORD
);
PreparedStatement statement = connection.prepareStatement(
"INSERT INTO wizard (first_name, last_name, birthday, birth_place, biography, is_muggle) VALUES (?, ?, ?, ?, ?, ?)",
Statement.RETURN_GENERATED_KEYS
);
statement.setString(1, wizard.getFirstName());
statement.setString(2, wizard.getLastName());
statement.setDate(3, wizard.getBirthday());
statement.setString(4, wizard.getBirthPlace());
statement.setString(5, wizard.getBiography());
statement.setBoolean(6, wizard.isMuggle());
if (statement.executeUpdate() != 1) {
throw new SQLException("failed to insert data");
}
ResultSet generatedKeys = statement.getGeneratedKeys();
if (generatedKeys.next()) {
Long id = generatedKeys.getLong(1);
wizard.setId(id);
return wizard;
} else {
throw new SQLException("failed to get inserted id");
}
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
Le seul changement notable est dans la méthode save :
Long id = generatedKeys.getLong(1);
wizard.setId(id);
return wizard;
En effet, avant de retourner l'objet enregistré, on lui précise quel est l'identifiant inséré en base de données.
Par convention, les identifiants sont des Long pour deux raisons :
-
il est possible que la taille d'un identifiant dépasse la capacité d'un entier.
-
utiliser la classe enveloppe
Longplutôt que le type primitiflongpermet d'avoir un identifiant null : pratique quand un objet n'a pas encore été enregistré dans une base de données !
Challenge
Administration des écoles de magie

Fait un fork du dépôt suivant : https://github.com/WildCodeSchool/quest-spring-jdbc-crud.
Tu reprends le développement d'un site de fan d'Harry Potter afin d'y ajouter des fonctionnalités. En l'état, le site permet l'administration complète des personnages de l'univers de la série.
La base de données est la même que celle de la quête précédente, cependant si tu l'as effacée, tu peux importer la base de données spring_jdbc_quest contenue dans le fichier suivant.
L'utilisateur est le même que la quête précédente : h4rryp0tt3r avec le mot de passe Horcrux4life! et tous les droits possibles sur la base de données spring_jdbc_quest.
Tu vas commencer par tester le site et voir comment tout cela fonctionne.
Attention à bien regarder toutes les classes du dépôt avant de commencer le challenge !
- Tester que le site fonctionne correctement
- Créer un CRUD fonctionnel pour les écoles
Critères de validation
- Le projet s'exécute bien sur la base de données et l'utilisateur requis.
- Il est possible d'afficher la liste de toutes les écoles.
- Il est possible de créer une école.
- Il est possible de modifier une école.
- Il est possible de supprimer une école.
- La classe
SchoolRepositoryimplémente bienCrudDao. - Le code est disponible sur GitHub.