Date de première publication : 2019/11/07
1. Préparation
1.1. Objectif et contexte
L'objectif de cet exercice est de continuer la découverte de SpringBoot avec l'utilisation de Java Persistance API (JPA).
Vous allez réaliser une application qui permet à un utilisateur de noter des tâches à faire et de les ordonner en "à faire", "en cours" et "terminé".

L'environnement de développement est le suivant :
- Java : Java SE 8, 11 ou 17
- Gradle 7+ ou Gradlew
- SpringBoot
Ce sujet utilise les documentations officielles de Spring Boot suivantes :
- "accessing datajpa"
- "relational data access"
- "accessing data mysql"
- "accessing data rest"
- "rest hateoas"
On ne cherche pas à manipuler une base non relationnelle et on ne cherche pas à faire une architecture REST (pas encore).
1.2. Quelques concepts
Grâce à Spring Data, Spring permet de manipuler tous les types de bases de données mais nous allons nous limiter aux bases relationnelles. On peut les manipuler à différents niveaux : rester au plus proche du SGBD choisi (JDBC avec connecteur) à une abstraction complète avec ORM (Object Relational Machine) nommée JPA.
Java |
ORM |
JDBC |
Base de données |
De nombreux moteurs sont utilisables pour Spring : Oracle, MySQL/MariaDB, posgreSQL, derby (aka JavaDB) et h2... Malheureusement, l'implémentation de JPA utilisée, Hibernate, ne supporte pas facilement actuellement SQLite (il y a pas de mal configuration à faire et il faut fournir un Dialect entre autres). On utilisera la base H2 (écrite en Java) en mode embedded (pas serveur) et fichier (pas inmemory). On aurait pu utiliser derby qui est fourni avec le JDK.
1.3. Modélisation et SQL
Nous allons gérer des tâches / post-its / notes. Ces tâches seront rangées par catégorie. Une tâche possède donc un contenu, une catégorie, une date de création et une date pour laquelle le travail doit être fait. Une catégorie est un simple nom.
Voici le code SQL pour générer les tables tasks
et categories
, le SQL est
1.4. Mise en place du projet
La meilleure manière de commencer est https://start.spring.io mais vous avez toutes les étapes pour le faire à la main :
Le fichier de configuration gradle build.gradle
est somme toute assez classique. Il ne faut pas oublier d'ajouter JPA et H2 (voire lombok) :
Il est ensuite nécessaire de créer une aborescence si vous ne l'avez pas déjà :
2. Manipuler une base avec JDBC
2.1. Création de la base
Nous allons coder une application principale qui sera capable de créer une base de départ (structure et données). Pour cela, on va analyser la ligne de commande (il faut implémenter l'interface CommandLineRunner
:
Deux attributs ont été déclarés : le premier log
va permettre d'afficher des informations sur l'exécution du programme (vous pourriez aussi utiliser les sorties standards), le second jdbcTemplate
est créé automatiquement par Spring et va permettre de manipuler la base de données directement avec JDBC.
Si le paramètre install
est détecté à l'exécution, la base sera créée sinon l'étape sera sautée.
Il ne faut pas oublier de spécifier dans quelle base de données nous travaillons (type, inmemory, serveur, ...). Cela se configure dans le fichier application.properties
. Par exemple, cela donne :
Le paramètre ddl-auto
est spécifique à Hibernate : il permet de spécifier si la structure de la base de données doit être créée par l'ORM si elle n'existe pas. Si la base est créée par JDBC, laissez le paramètre à none
sinon passez le à update
Le code de génération (ddl :-)) qui s'appuie sur le modèle de données se trouve ci-dessous :
2.2. Utilisation de la base avec JDBC
On peut vérifer que les tables sont correctement créées avec le même genre de code : le programme peut réagir à une commande test par exemple. Le premier code permet de lire la table categories
avec une lamda et stocke les informations dans une liste :
Le deuxième code permet de lire la table tasks
2.3. Console h2
Tant que votre serveur est en train de tourner, une console h2 est disponible à l'url http://localhost:8080/h2-console/

Connectez-vous sans login ni mot de passe en utilisant la chaine de connexion du fichier application.properties
2.4. Affichage web
A l'instar du TP précédent, vous pouvez créer une vue pour afficher les tâches à faire. Il suffit d'ajouter la liste des choses à faire au modèle quand on écrit le contrôleur. Si on passe la liste avec un modèle, cela va donner :
On utilise la méthode get()
car row
est une Map
On va pas aller plus loin sur l'utilisation de JDBC.
3. Manipulation avec JPA (Hibernate)
Ce que l'on a fait est un peu trop "SGBD" à mon goût. Il est possible de gérer des objets, appelés entités, plutôt que des tuples de table. Pour ce faire, il faut créer des classes et expliciter, si nécessaire, la correspondance (mapping) entre l'attribut de l'objet et la colonne de la table. La configuration par convention est appliquée sauf si elle est modifiée par annotation.
Voici le code de la classe Category
:
Et celui de la classe Task
:
Les clés primaires avec la génération automatique sont spécifiées @Id
et @GeneratedValue
, les clés étrangères également (@JoinColumn
et @ManyToOne
. Si les noms de table ou de colonne sont différents de la configuration par convention, un paramètre name
vient corriger cela (@Table
et @Column
.
Les getters et les setters n'ont pas été écrits. Vous pouvez :
- les écrire à la main
- les générer avec votre EDI
- utiliser le projet lombok pour bénéficier des annotations
@Getter
et@Setter
sur les attributs.
Si vous oubliez un setter par exemple, le binding ne vous permettra pas de mettre à jour l'objet mais vous n'aurez aucune erreur.
Ces classes vont permettre de créer des entités gérées par un repository à écrire pour chaque entité. Le repository est le DAO de SpringBoot. Vous pouvez utiliser CrudRepository
ou son interface fille JpaRepository
Grâce à Spring, il n'est pas nécessaire d'écrire plus. Les méthodes vont être implémentées automatiquement. Vous devez tout de même écrire l'interface TaskRepository
Dans l'application, pour pouvoir utiliser un tel repository, il faudra encore laisser la magie de Spring faire :
Ce repository permet de faire toutes les requêtes nécessaires et les créations voulues. Vous pouvez par exemple ajouter de nouveaux tuples en base avec le code suivant :
On va exploiter les requêtes dans la section suivante.
4. Interface utilisateur
Voici les pages et les actions que l'on aimerait réaliser :
/ | GET | Lister toutes les tâches : |
/tasks | GET | Lister toutes les tâches |
/tasks/new | GET | Afficher une page pour créer une tâche |
/tasks | POST | Créer une nouvelle tâche |
Et si on voulait une manipulation complète :
/tasks/:id | GET | Afficher la tâche donnée |
/tasks/:id/edit | GET | Afficher un formulaire pour modifier la tâche |
/tasks/:id | PATCH/PUT | Modifie la tâche donnée |
/tasks/:id | DELETE | Efface la tâche donnée |
4.1. Affichage des tâches
Nous allons écrire une page web - un template - pour afficher les tâches. La navigation implique d'écrire un contrôleur :
Voici maintenant le template nécessaire pour afficher toutes ces tâches :
La liste des tâches est passée en paramètre au modèle de la page et l'attribut th:each
permet d'itérer chacun des objets.
tasks
est un Iterable
de Task
. Écrire t.content
en Thymeleaf n'est pas un accès à l'attribut mais il y a bien un appel aux méthodes getContent()
et setContent()
de l'objet.
4.2. Ajouter une tâche
Je vous propose d'ajouter une méthode au contrôleur pour ajouter une nouvelle tâche. Les données seront tirées d'un formulaire (une page à concevoir). Si vous respectez les règles usuelles de nommage, la création se fera avec la même URL que la consultation des tâches mais avec le verbe POST
Pour afficher une tâche particulière, il faut modifier l'URL d'accès :
Cela peut se faire simplement avec une méthode du contrôleur :
XXX est bien entendu relatif au verbe HTTP : GET
pour voir, PUT/PATCH
pour modifier et DELETE
pour effacer ...
C'est un peu fastidieux mais faisable bien entendu !!!
5. Une pincée poignée de présentation
En plus de l'exemple, en début de TP, j'aime bien les présentations suivantes :
- https://code.tutsplus.com/tutorials/create-a-sticky-note-effect-in-5-easy-steps-with-css3-and-html5--net-13934
- https://www.bootdey.com/snippets/view/notes-dashboard
En fonction de ce que vous savez ou vous voulez apprendre : on peut faire cela avec un framework CSS ou pas, un framework JS ou pas.
La suite de l'exercice va se faire suivant l'exemple donné plus haut : les tâches sont réparties sur trois tableaux
Si vous ne savez pas où vous en êtes, voici une piste de démarrage..
Nous avons besoin de lister les tâches par categorie. Cette méthode n'est pas disponible dans taskRepository
, il faut l'ajouter puis il faut modifier la page d'affichage pour afficher les listes.
Pour mettre du style dans la page, deux possibilités :
- une balise
style
dans l'entête - une balise
link
pour donner le style dans un fichier d'extension .css
Voilà ce qu'il faut faire :
- Afficher les trois colonnes les unes à côté des autres
- Faire que les titres des colonnes ne bougent pas
- Afficher les taches sous forme de postits : des rectangles de même taille ...
On part du principe qu'il n'y a que 3 colonnes alors que les categories sont lues dans une base. On pourrait s'amuser à générer le css grâce à thymeleaf.
5.1. Afficher les colonnes
On peut utiliser le mode grille ou flex ... ou inline-block ou flottant ...
5.2. Afficher les tâches
6. Et le déplacement dans tout ça ?
Pour faire cela, il va falloir écrire du javascript côté client
- changement de la catégorie avec un requête ajax ou à défaut, rechargement de la page après opération
- drag and drop des posts-its/tâches à la souris
Je vous propose d'écrire le code pour changer la catégorie d'une tâche. Par exemple, avec l'URL : /tasks/1/done. Le verbe à utiliser est PATCH (on ne change qu'une partie de la ressource. Si ce verbe n'est pas supporté par le conteneur de servlets, vous utiliserez PUT)
Le code javascript suivant permet de changer la catégorie de la tâche 1 au clic de la souris (à mettre dans une balise <script>
à la toute fin de la balise body
). C'est de l'AJAX avec la méthode FETCH :
On pourrait utiliser les attributs de données personnalisées sur une balise pour connaître l'identifiant de la tâche ou analyser l'identifiant du composant web. En thymeleaf, cela donne quelque chose comme : th:data-id
et cela s'utilise avec element.dataset.id
en javascript.
Pour la dernière étape, le drag & drop, je vous conseille le lien suivant ;-) Attention, ce qui est déplacé est beaucoup plus fragile que des posts-its.