Exercice - Utilisation de JDBC⚓︎
1. En base de données⚓︎
Nous allons créer un schéma et une table sur celui-ci. Il suffit pour cela d'exécuter le script suivant sur la base de données :
create database gestion_notes;
CREATE TABLE `gestion_notes`.`note` (
`id` int NOT NULL auto_increment, /*(1)!*/
`nom` varchar(45) DEFAULT NULL,
`prenom` varchar(45) DEFAULT NULL,
`note` decimal(4,2) DEFAULT NULL,
PRIMARY KEY (`id`)
);
- Permet d'auto-incrémenter le champ à chaque nouvelle insertion en BDD.
Avant toutes choses ...
On commencera par créer un projet Java Web Dynamique, qu'on appelera GestionNotes (toujours avec le runtime Tomcat 9).
2. Création de la data source⚓︎
Téléchargement du pilote
Tomcat est un serveur très léger, qui par défaut, contient uniquement un conteneur de Servlet. Pour nous connecter à une base de données, il faut donc ajouter un JAR prévu à cet effet (c'est le pilote qui permet de se connecter à une base de données MySQL - il en faut par exemple un autre pour se connecter à une base de données Oracle).
Il est disponible ici (https://dev.mysql.com/downloads/connector/j/).
Sur cette page, sélectionner "Plateform Independent", puis télécharger le zip (il n'est pas nécessaire de se connecter - il suffit de cliquer sur No thanks, just start my download). Dans ce zip, seul le fichier 📄mysql-connector-java-a.b.c.jar
nous interesse.
Il faut le déposer dans le dossier 📂webapp/WEB-INF/lib
du projet Web dynamique.
Ce JAR est nécessaire uniquement à l'exécution, pas à la compilation. Il est donc inutile de l'ajouter dans le classpath du projet.
Configuration du serveur
Pour accéder aux données, il faut commencer par créer la "source de données" sur le serveur, c'est-à-dire indiquer au serveur tomcat où est la base de données.
Il y a plusieurs façon de faire, nous allons ici la configurer directement sur le serveur.
Pour cela, dans Eclipse, losqu'on a ajouté le serveur Tomcat, un projet Servers a été ajouté. Ce projet contient notamment un fichier qui va nous intéresser : 📄context.xml
. Dans ce fichier 📄context.xml
, rajouter le code suivant, en adaptant les valeurs des attributs url
, username
et password
si besoin (lorsqu'on utilise phpMyAdmin, le mot de passe par défaut est ""
par exemple) :
<Resource name="jdbc/MySqlGestionNotes"
auth="Container"
type="javax.sql.DataSource"
driverClassName="com.mysql.cj.jdbc.Driver"
url="jdbc:mysql://localhost:3306/gestion_notes"
username="root"
password="admin"
/>
3. Couche "Modèle"⚓︎
Créer le bean représentant la couche "modèle" de notre application, c'est-à-dire permettant de faire le lien avec la table NOTE
en base de données.
On appelera ce bean fr.univtours.polytech.gestionnotes.model.NoteBean
. Ce bean est très simple, puisqu'il ne comporte que les propriétés correspondant aux champs de le table, et les accesseurs et mutateurs associés :
classDiagram
NoteBean
class NoteBean{
-id: Integer
-nom: String
-prenom: String
-note: Float
+getId() Integer
+setId(id: Integer)
+getNom() String
+setNom(nom: String)
+getPrenom() String
+setPrenom(prenom: String)
+getNote() Float
+setNote(note: Float)
}
4. Couche "Accès aux données"⚓︎
Nous allons maintenant créer la partie DAO - pour Data Access Object. Cette couche contient :
- une interface -
fr.univtours.polytech.gestionnotes.dao.NotesDAO
- qui définit les accès possibles aux données (la plupart du temps, ce sont les CRUD), - et une classe -
fr.univtours.polytech.gestionnotes.dao.NotesDAOImplJDBC
- qui implémente cette interface.
Voici les accès qui seront proposés ici :
- La liste de toutes les notes (
SELECT * from note;
). - Une seule note avec son identifiant (
SELECT * from note where id = ?;
). - L'insertion d'une note (
INSERT INTO NOTE (id, nom, prenom, note) values (?, ?, ?, ?);
). - La mise à jour d'une note (
UPDATE NOTE set nom=?, prenom=?, note=? where id=?;
).
Le code de l'interface
public interface NotesDAO {
public List<NoteBean> getNotesList();
public NoteBean getNote(Integer id);
public void insertNote(NoteBean note);
public void updateNote(NoteBean note);
}
Le début du code de l'implémentation, qu'il faut compléter
public class NotesDAOImplJDBC implements NotesDAO {
/**
* Le nom indiqué dans le fichier context.xml.
*/
private static final String RESOURCE_NAME = "jdbc/MySqlGestionNotes";
/**
* Permet d'ouvrir la connexion qui a été définie dans les fichiers XML du
* serveur Tomcat.
*
* @return L'object {@link Connection} correspondant.
* @throws SQLException
* @throws NamingException
*/
public Connection createConnection() throws SQLException, NamingException {
InitialContext ic = new InitialContext();
Context ctx = (Context) ic.lookup("java:comp/env");
DataSource ds = (DataSource) ctx.lookup(RESOURCE_NAME);
return ds.getConnection();
}
@Override
public List<NoteBean> getNotesList() {
Statement st = null;
ResultSet rs = null;
Connection connection = null;
List<NoteBean> notesList = null;
try {
// Lecture de la table NOTE :
final String sql = "SELECT * from note;";
connection = createConnection();
st = connection.createStatement();
rs = st.executeQuery(sql);
notesList = mapResultSetToList(rs);
} catch (Exception e) {
// S'il y a eu un problème, on le fait remonter.
throw new RuntimeException(e);
} finally {
try {
// Dans tous les cas, on ferme tout ce qui doit l'être.
st.close();
connection.close();
} catch (Exception e) {
// S'il y a eu un problème, on le fait remonter.
throw new RuntimeException(e);
}
}
return notesList;
}
@Override
public NoteBean getNote(Integer id) {
PreparedStatement st = null;
ResultSet rs = null;
Connection connection = null;
List<NoteBean> notesList = null;
try {
// Lecture de la table NOTE :
final String sql = "SELECT * from note where id = ?;";
connection = createConnection();
// On utilise ici un PreparedStatement plutôt qu'un Statement pour des raisons de sécurités.
st = connection.prepareStatement(sql);
st.setInt(1, id);
rs = st.executeQuery();
notesList = mapResultSetToList(rs);
} catch (Exception e) {
// S'il y a eu un problème, on le fait remonter.
throw new RuntimeException(e);
} finally {
try {
// Dans tous les cas, on ferme tout ce qui doit l'être.
st.close();
connection.close();
} catch (Exception e) {
// S'il y a eu un problème, on le fait remonter.
throw new RuntimeException(e);
}
}
if (notesList.size() != 1) {
// Si on n'a pas récupéré exactement 1 seul enregistrement, c'est qu'il y a eu un problème.
throw new RuntimeException("Problème à la récupération de l'enregistrement " + id);
}
return notesList.get(0);
}
@Override
public void insertNote(NoteBean note) {
// On utilisera ici des PreparedStatement plutôt que des Statement pour des raisons de sécurité.
PreparedStatement st = null;
Connection connection = null;
try {
connection = createConnection();
final String sqlInsert = "INSERT INTO NOTE (nom, prenom, note) values (?, ?, ?);";
st = connection.prepareStatement(sqlInsert);
st.setString(1, note.getNom());
st.setString(2, note.getPrenom());
st.setDouble(3, note.getNote());
st.executeUpdate();
} catch (Exception e) {
// S'il y a eu un problème, on le fait remonter.
throw new RuntimeException(e);
} finally {
try {
// Dans tous les cas, on ferme tout ce qui doit l'être.
st.close();
connection.close();
} catch (Exception e) {
// S'il y a eu un problème, on le fait remonter.
throw new RuntimeException(e);
}
}
}
@Override
public void updateNote(NoteBean note) {
...
// On utilisera ici des PreparedStatement plutôt que des Statement pour des raisons de sécurité.
// Cette méthode n'est à implémenter que dans un second temps, lorsque ce TD est terminé.
}
/**
* Permet d'effectuer le mapping entre le {@link ResultSet} renvoyé par la
* requête, et la liste d'objets {@link NoteBean}.
*
* @param rs Le {@link ResultSet} à transformer.
* @return La liste de {@link NoteBean} qui correspond.
* @throws SQLException
*/
private final List<NoteBean> mapResultSetToList(final ResultSet rs) throws SQLException {
List<NoteBean> notesList = new ArrayList<NoteBean>();
while (rs.next()) {
// Pour chaque ligne de la table,
// on instancie un nouveau NotesBean.
final NoteBean noteBean = new NoteBean();
noteBean.setId(rs.getInt("id")); // Il faut indiquer le nom du champ en BDD, ici, 'id'.
noteBean.setNom(...);
...
...
// On ajoute ce bean à la liste des résultats.
notesList.add(noteBean);
}
return notesList;
}
Une requête en BDD de type SELECT
renvoie un objet ResultSet
. Cette objet doit être parcouru, et chaque ligne qu'il contient doit être transformée en NoteBean
. C'est l'utilité de la méthode mapResultSetToList
.
5. Couche "Services métiers"⚓︎
De manière semblable à la couche DAO, il faut créer :
- une interface -
fr.univtours.polytech.gestionnotes.business.NotesBusiness
- qui définit les services métier, - et une classe -
fr.univtours.polytech.gestionnotes.business.NotesBusinessImpl
- qui implémente cette interface.
Voici les services qui seront proposés ici :
- La liste de toutes les notes.
- Une seule note avec son identifiant.
- La création d'une note.
- La mise à jour d'une note.
- Le calcul de la moyenne de toutes les notes.
Le code de l'interface
public interface NotesBusiness {
public List<NoteBean> getNotesList();
public NoteBean getNote(Integer id);
public void insertNote(NoteBean note);
public void updateNote(NoteBean note);
public Float computeMean(List<NoteBean> notesList);
}
Le code de la classe, qu'il faut compléter
public class NotesBusinessImpl implements NotesBusiness {
private NotesDAO dao;
public NotesBusinessImpl() {
this.dao = new NotesDAOImplJDBC();
}
@Override
public List<NoteBean> getNotesList() {
return dao.getNotesList();
}
@Override
public NoteBean getNote(Integer id) {
return dao.getNote(id);
}
@Override
public void insertNote(NoteBean note) {
dao.insertNote(note);
}
@Override
public void updateNote(NoteBean note) {
dao.updateNote(note);
}
@Override
public Float computeMean(List<NoteBean> notesList) {
...
}
}
6. Couche présentation - implémentation de la vue et du contrôleur⚓︎
Nous avons implémenté le modèle, il reste à implémenter le contrôleur (ce sont les différentes Servlets) et la vue (ce sont les 3 JSPs).
Depuis le contrôleur, on peut appeler les services métier. Par exemple, pour récupèrer la liste des notes présentes en BDD. Pour cela, comme nous l'avons fait dans un TD précédent, nous allons créer le lien avec la couche service dans la méthode init
de chaque servlet :
private NotesBusiness business;
@Override
public void init() throws ServletException {
this.business = new NotesBusinessImpl();
}
Depuis la vue, il faudra utiliser la JSTL (pour Java Standard Tag Library) pour utiliser des boucles "pour" et des instructions conditionnelles "if".
Utilisation de la JSTL
Pour cela, il faut ajouter la directive suivante dans les JSP utilisant la JSTL :
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%><!--(1)!-->
- On utilise les balises
c:forEach
,c:if
,c:out
, ... car le préfixe indiqué à l'import de la bibliothèque estc
. C'est la valeur "classique" de ce préfixe.
Enfin, lorsqu'on utilise un serveur Tomcat (ce n'est pas le cas avec un serveur WildFly), il faut ajouter les deux JARs dans le dossier 📂webapp/WEB-INF/lib
:
Aide pour la mise en place d'une boucle pour
Si la liste des objets NoteBean
a été placé dans la requête avec la clef LISTE_NOTES
, on pourra utiliser le code suivant, à compléter :
<table>
<tr>
<th>Prénom</th>
<th>Nom</th>
<th>Note</th>
</tr>
<c:forEach items="${requestScope.LISTE_NOTES}" var="note">
<tr>
<td>${note.prenom}</td>
<td>${note.nom}</td>
<td>${note.note}
</tr>
</c:forEach>
</table>
Aide pour la mise en place d'une instruction conditionnelle
Pour l'affichage de la moyenne, on peut imaginer n'afficher le bloc correspondant que lorsque il y a au moins une note.
Par exemple, si la moyenne, lorsqu'il y a au moins une note donc, est stockée dans la requête avec la clef MOYENNE
, on peut utiliser le code suivant :
<c:if test="${not empty requestScope.MOYENNE}">
La moyenne est de ${requestScope.MOYENNE}
</c:if>
Vérification
Vérifier maintenant que l'application est bien fonctionnelle.