Afin d'alléger la charge que le SGBD doit actuellement supporter à chaque requête, nous allons utiliser une technique très simple : nous allons précharger un certain nombre de connexions à la base de données, et les réutiliser. L'expression employée pour nommer cette pratique est le « connection pooling »
Le pool de connexions va pré-initialiser un nombre donné de connexions au SGBD lorsque l'application démarre. Autrement dit, il va créer plusieurs objets Connection et les garder ouverts et bien au chaud.
Ensuite, le pool de connexions va se charger de distribuer ses objets Connection aux méthodes de l'application qui en ont besoin. Concrètement, cela signifie que ces méthodes ne vont plus faire appel à DriverManager.getConnection(), mais plutôt à quelque chose comme pool.getConnection().
Enfin, puisque l'objectif du système est de partager un nombre prédéfini de ressources, un appel à la méthode Connection.close() ne devra bien entendu pas provoquer la fermeture réelle d'une connexion ! En lieu et place, c'est tout simplement un renvoi dans le pool de l'objet Connection qui va avoir lieu. De cette manière, et seulement de cette manière, la boucle est bouclée : l'objet Connection inutilisé retourne à la source, et est alors prêt à être à nouveau distribué.
Une DataSource n'est qu'une interface, il est donc nécessaire d'en écrire une implémentation. Rassurez-vous, nous n'allons pas nous occuper de cette tâche : il existe plusieurs bibliothèques, libres et gratuites, qui ont été créées par des équipes de développeurs expérimentés et validées par des années d'utilisation. Sans être exhaustif, voici une liste des solutions les plus couramment rencontrées :
Apache DBCP
BoneCP
c3p0
DBPool
La connexion à une base de données est une étape coûteuse en termes de temps et de performances.
Il est nécessaire d'initialiser un nombre prédéfini de connexions, et de les partager/distribuer/réutiliser pour chaque requête entrante : c'est le principe du pool de connexions.
Lorsqu'un pool de connexions est en place, un appel à la méthode connexion.close() ne ferme pas littéralement une connexion, mais la renvoie simplement au pool.
La méthode getConnection() étant centralisée et définie dans notre Factory, il nous est très aisé de modifier son comportement.
Un pool de connexions se base sur le principe d'une DataSource, objet qu'il est vivement recommandé d'utiliser en lieu et place du DriverManager.
BoneCP est une solution de pooling très efficace, aisément configurable et intégrable à n'importe quelle application Java EE.
PreparedStatement
est une nouvelle interface qui implémente l'interface Statement
. Comme son nom l'indique, cet objet permet de créer des requêtes préparées.PreparedStatement
il présente trois différences majeures avec un Statement
classique :
PreparedStatement
peut permettre de pré-compiler une requête SQL pour réduire le temps d'exécution.ResultSet
, il existe une méthode par type de donnée :
preparedStatement.setInt()
pour définir un entier.preparedStatement.setString()
pour définir une chaîne de caractères.preparedStatement.setBoolean()
pour définir un booléen.preparedStatement
pour attendent simplement deux arguments :
/* Création de l'objet gérant la requête préparée définie */
PreparedStatement preparedStatement = connexion.prepareStatement( "SELECT id, email, mot_de_passe, nom FROM Utilisateur WHERE email = ?;" );
/* Récuperation et assignation d'un parametre pour la requête */
preparedStatement.setString( 1, request.getParameter( "email" ));
/* Execution de la requête */
resultat = preparedStatement.executeQuery();
/* Récupération des données du résultat de la requête de lecture */
while ( resultat.next() ) {
int idUtilisateur = resultat.getInt( "id" );
/* Traiter ici les valeurs récupérées. */
}
/* Création de l'objet gérant les requêtes préparées */
preparedStatement = connexion.prepareStatement( "INSERT INTO Utilisateur (email, mot_de_passe, nom, date_inscription) VALUES(?, MD5(?), ?, NOW());" );
/*
* Récupération des paramètres d'URL saisis par l'utilisateur
* Remplissage des paramètres de la requête grâce aux méthodes
* setXXX() mises à disposition par l'objet PreparedStatement.
*/
preparedStatement.setString( 1, request.getParameter("email"));
preparedStatement.setString( 2, request.getParameter("motdepasse"));
preparedStatement.setString( 3, request.getParameter("nom"));
/* Exécution de la requête */
int statut = preparedStatement.executeUpdate();
executeUpdate()
retourne un entier représentant le nombre de lignes affectées par la requête réalisée ou 0 en cas d'échec.
/* Exécution d'une requête d'écriture */
int statut = statement.executeUpdate( "INSERT INTO Utilisateur (email, mot_de_passe, nom, date_inscription) VALUES ('jmarc@mail.fr', MD5('lavieestbelle78'), 'jean-marc', NOW());" );
Lorsque vous effectuez une modification sur une table de votre base de données via la méthode statement.executeUpdate()
, celle-ci renvoie des informations différentes selon le type de la requête effectuée :
INSERT
renvoie 0 en cas d'échec de la requête d'insertion, et 1 en cas de succès.UPDATE
ou d'un DELETE
renvoie le nombre de lignes respectivement mises à jour ou supprimées.CREATE
, ou de toute autre requête ne retournant rien, renvoie 0./* Exécution d'une requête d'écriture */
int statut = statement.executeUpdate( "INSERT INTO Utilisateur (email, mot_de_passe, nom, date_inscription) VALUES ('jmarc@mail.fr', MD5('lavieestbelle78'), 'jean-marc', NOW());" );
/*
* Exécution d'une requête d'écriture avec renvoi de l'id auto-généré
*/
int statut = statement.executeUpdate( "..." , Statement.RETURN_GENERATED_KEYS);
// Récupération de l'id auto-généré par la requête d'insertion.
resultat = statement.getGeneratedKeys();
// Parcours du ResultSet et formatage pour affichage de la valeur qu'il contient dans la JSP finale.
while ( resultat.next() ) {
messages.add( "ID retourné lors de la requête d'insertion :" + resultat.getInt( 1 ) );
}
/*
* Exécution d'une requête préparé d'écriture avec renvoi de l'id auto-généré
*/
PreparedStatement preparedStatement = connexion.prepareStatement( "INSERT INTO Utilisateur (email, mot_de_passe, nom, date_inscription) VALUES ('jmarc@mail.fr', MD5('lavieestbelle78'), 'jean-marc', NOW());", Statement.RETURN_GENERATED_KEYS );
preparedStatement.executeQuery();
resultat = preparedStatement.getGeneratedKeys();
while ( resultat.next() ) {
messages.add( "ID retourné lors de la requête d'insertion :" + resultat.getInt( 1 ) );
}
executeQuery()
retourne un objet de type ResultSet
contenant le résultat de la requête :
* Exécution d'une requête de lecture */
ResultSet resultat = statement.executeQuery( "SELECT id, email, mot_de_passe, nom FROM Utilisateur;" );
statement.executeQuery()
retourne un objet de typeResultSet
. Vous pouvez le voir comme un tableau, qui contient les éventuelles données retournées par la base de données sous forme de lignes.next()
, qui permet de déplacer le curseur à la ligne suivante. Elle retourne un booléen, initialisé à true
tant qu'il reste des données à parcourir.ResultSet
, son curseur est par défaut positionné avant la première ligne de données. Ainsi, il est nécessaire de se déplacer d'un cran vers l'avant pour pouvoir commencer à lire les données contenues dans l'objet.SQLException
.ResultSet
propose une méthode de récupération par type de données :
resultat.getInt()
pour récupérer un entierresultat.getString()
pour récupérer une chaîne de caractèresresultat.getBoolean()
pour récupérer un booléenResultSet
existe sous deux formes différentes :
/* Exécution d'une requête de lecture */
ResultSet resultat = statement.executeQuery( "SELECT id, email, mot_de_passe, nom FROM Utilisateur;" );
/* Récupération des données du résultat de la requête de lecture */
while ( resultat.next() ) {
int idUtilisateur = resultat.getInt( "id" );
String emailUtilisateur = resultat.getString( "email" );
String motDePasseUtilisateur = resultat.getString( "mot_de_passe" );
String nomUtilisateur = resultat.getString( "nom" );
/* Traiter ici les valeurs récupérées. */
}
jdbc:mysql://hôte:port/nom_de_la_bdd
.DriverManager.getConnection()
qui retourne un objetConnection
.connexion.createStatement()
retourne un objetStatement
, qui permet d'effectuer des requêtes via notamment ses méthodes executeQuery()
et executeUpdate()
.connexion.prepareStatement()
retourne un objet PreparedStatement
, qui permet d'effectuer des requêtes de manière sécurisée, via notamment ses méthodes executeQuery()
et executeUpdate()
.close()
./* Connexion à la base de données */
String url = "jdbc:mysql://nomhote:port/nombdd";
String utilisateur = "user";
String motDePasse = "pass";
Connection connexion = null;
try {
connexion = DriverManager.getConnection( url, utilisateur, motDePasse );
/* Ici, nous placerons nos requêtes vers la BDD */
} catch ( SQLException e ) {
/* Gérer les éventuelles erreurs ici */
} finally {
if ( connexion != null )
try {
connexion.close();
} catch ( SQLException ignore ) {
/* Si une erreur survient lors de la fermeture, il suffit de l'ignorer. */
}
}
typeStatement
.createStatement()
de l'objet Connection
précédemment obtenu./* Création de l'objet gérant les requêtes depuis l'objet connexion dans le try */
Statement statement = connexion.createStatement();
/* Avec une requête préparé */
PreparedStatement preparedStatement = connexion.prepareStatement( "..." );
Statement
initialisé, il devient alors possible d'exécuter une requête.executeQuery()
: cette méthode est dédiée à la lecture de données via une requête de type SELECT
.executeUpdate()
: cette méthode est réservée à l'exécution de requêtes ayant un effet sur la base de données (écriture ou suppression), typiquement les requêtes de type INSERT
, UPDATE
, DELETE
, etc.execute()
et executeBatch()
(voir http://docs.oracle.com/javase/6/docs/api/java/sql/Statement.html).StatementetResultSet
initialisés au sein d'une connexion :ResultSet
, puis le Statement
et enfin l'objet Connection
. Les exceptions éventuelles, envoyées en cas de ressources déjà fermées ou non disponibles, peuvent être ignorées comme c'est le cas dans ce code d'exemple. Vous pouvez bien évidemment choisir de les prendre en compte, par exemple en les enregistrant dans un fichier de logs.Connection connexion = null;
Statement statement = null;
ResultSet resultat = null;
try {
/*
* Ouverture de la connexion, initialisation d'un Statement, initialisation d'un ResultSet, etc.
*/
} catch ( SQLException e ) {
/* Traiter les erreurs éventuelles ici. */
} finally {
if ( resultat != null ) {
try {
/* On commence par fermer le ResultSet */
resultat.close();
} catch ( SQLException ignore ) {
}
}
if ( statement != null ) {
try {
/* Puis on ferme le Statement */
statement.close();
} catch ( SQLException ignore ) {
}
}
if ( connexion != null ) {
try {
/* Et enfin on ferme la connexion */
connexion.close();
} catch ( SQLException ignore ) {
}
}
}