Aller au menu - Aller au contenu

Icône N'avoir qu'une instance de sa connexion

Mise à jour : 17/06/2009
3 526 visites depuis 7 jours, dont 146 sur ce chapitre classé 45/786
L'exécution de commandes SQL n'a plus de secret pour vous, je le sais, c'est moi qui vous ai formés ! :p
Non, je blague...

Après vous avoir fait découvrir tout ça, je me suis dis : montrer une approche un peu plus objet ne serait pas du luxe !

C'est vrai, se connecter sans arrêt à notre base de données commence à être fastidieux. Je vous propose donc d'y remédier avec ce chapitre. :)
Sommaire du chapitre :
Icône du chapitre
Chapitre précédent Sommaire Chapitre suivant

Pourquoi se connecter qu'une seule fois ?

Pourquoi tu veux absolument qu'on aie une seule instance de notre objet Connection ?

Parce que ça ne sert pas à grand-chose de réinitialiser la connexion à votre BDD. Rappelez-vous que la connexion sert à faire le pont entre votre base et votre application. Pourquoi voulez-vous que votre application se connecte à chaque fois à votre BDD ?
Une fois la connexion effective, pourquoi vouloir la refaire ? Votre application et votre BDD peuvent discuter !

Bon, c'est vrai qu'avec du recul, ça paraît superflu...
Du coup, comment tu fais pour garantir qu'une seule instance de Connection existe dans l'application ?


C'est ici que le pattern singleton arrive !
Ce pattern est peut-être l'un des plus simples à comprendre même s'il va y avoir un point qui va vous faire bondir... ^^
Le principe de base de ce pattern est d'interdire l'instanciation d'une classe !

:waw:
On peut savoir comment tu comptes t'y prendre ?

C'est ça qui va vous faire bondir : avec un constructeur déclaré private !
Nul ne sert de lambiner, voyons comment rendre tout ceci possible...

Le pattern singleton

Alors ce que nous voulons, c'est une seule instance de notre connexion ; voici une classe qui permet ça :

Code : Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package com.sdz.connection;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class SdzConnection{

	/**
	 * URL de connection
	 */
	private String url = "jdbc:postgresql://localhost:5432/Ecole";
	/**
	 * Nom du user
	 */
	private String user = "postgres";
	/**
	 * Mot de passe du user
	 */
	private String passwd = "postgres";
	/**
	 * Objet Connection
	 */
	private static Connection connect;
	
	/**
	 * Constructeur privé
	 */
	private SdzConnection(){
		try {
			connect = DriverManager.getConnection(url, user, passwd);
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Méthode qui va nous retourner notre instance
	 * et la créer si elle n'existe pas...
	 * @return
	 */
	public static Connection getInstance(){
		if(connect == null){
			new SdzConnection();
		}
		return connect;	
	}	
}


Nous avons ici une classe avec un constructeur privé (même si, ici, ce n'était pas nécessaire) : du coup, impossible d'avoir une instance de cet objet et impossible d'accéder aux attributs puisqu'ils sont déclarés private !
Notre objet Connection est instancié dans le constructeur privé et la seule méthode accessible de l'extérieur de la classe est getInstance() . C'est donc cette méthode qui aura pour rôle de créer la connexion lorsque celle-ci n'existe pas, et seulement dans ce cas.

Pour en être bien sûrs, nous allons faire un petit test... ^^
Voici le code un peu modifié de la méthode getInstance() ...

Code : Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public static Connection getInstance(){
	if(connect == null){
		new SdzConnection();
		System.out.println("INSTANCIATION DE LA CONNEXION SQL ! ");
	}
	else{
		System.out.println("CONNEXION SQL EXISTANTE ! ");
	}
	return connect;	
}


Ceci a pour but de voir quand la connexion est réellement créée. Ensuite, il ne nous manque plus qu'un code de test. Oh ! Ben ça alors ! J'en ai un sous la main :

Code : Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.sdz.connection;

import java.sql.DatabaseMetaData;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;

public class Test {

	public static void main(String[] args) {
			
		try {
			//1
			PreparedStatement prepare = SdzConnection.getInstance().prepareStatement("SELECT * FROM classe WHERE cls_nom = ?");
			//2
			Statement state = SdzConnection.getInstance().createStatement();
			//3
			SdzConnection.getInstance().setAutoCommit(false);
			//Et 4 appels à la méthode getInstance()
			DatabaseMetaData meta = SdzConnection.getInstance().getMetaData();
			
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}
}


Il y a 4 appels à la méthode en question, que croyez-vous que ce code va afficher ?

Secret (cliquez pour afficher)
Image utilisateur


Vous avez la preuve que l'instanciation ne se fait qu'une seule fois et donc, que notre connexion à la BDD est unique !
Bon, ça c'est compris, par contre, pourquoi tu disais que le constructeur n'était pas nécessaire ?

Tout simplement parce que nous aurions pu avoir cela à la place et ça aurait tout aussi bien fonctionné :

Code : Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package com.sdz.connection;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class SdzConnection{

	/**
	 * URL de connection
	 */
	private static String url = "jdbc:postgresql://localhost:5432/Ecole";
	/**
	 * Nom du user
	 */
	private static String user = "postgres";
	/**
	 * Mot de passe du user
	 */
	private static String passwd = "postgres";
	/**
	 * Objet Connection
	 */
	private static Connection connect;
	
	/**
	 * Méthode qui va nous retourner notre instance
	 * et la créer si elle n'existe pas...
	 * @return
	 */
	public static Connection getInstance(){
		if(connect == null){
			try {
				connect = DriverManager.getConnection(url, user, passwd);
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}		
		return connect;	
	}	
}

Par contre, vous devrez rajouter la déclaration static de vos paramètres de connexion...

Vous pouvez relancer le code de test, vous verrez qu'il fonctionne toujours !
J'ai mis un constructeur privé d'abord car vous deviez savoir que cela existait, mais c'était superflu dans notre cas...

D'accord, mais j'ai une application multi-threads, tu es sûr qu'il n'y aura pas de conflit ?

Hum ! Vous savez déjà répondre à cette question si vous avez lu le chapitre sur les threads du tuto Java !
Il vous suffit de synchroniser la méthode getInstance() et le tour est joué...
Mais, parce qu'il y a un mais... cette méthode ne règle le problème qu'avant que la connexion ne soit instanciée. Autrement dit, une fois la connexion instanciée, la synchronisation ne sert plus à rien... :euh:

Le problème de multi-threading ne se pose pas vraiment pour une connexion à une BDD puisque ce singleton sert surtout de passerelle entre votre BDD et votre application, mais il peut y avoir d'autres objets que des connexions SQL qui ne doivent être instanciés qu'une fois et tous ne sont pas aussi laxistes concernant le multi-threading...

Voyons comment parfaire ce pattern avec un autre exemple qu'une connexion SQL...

Le singleton dans tous ces états

Alors... Nous allons travailler avec un autre exemple et vu que j'étais très inspiré, voici notre super singleton :

Code : Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package com.sdz.connection;

public class SdzSingleton {

	/**
	 * Le singleton
	 */
	private static SdzSingleton single;
	/**
	 * Variable d'instance
	 */
	private String name = "";
	
	/**
	 * Constructeur privé
	 */
	private SdzSingleton(){
		this.name = "Mon singleton";
		System.out.println("\t\tCREATION DE L'INSTANCE ! ! !");
	}
	
	/**
	 * Méthode d'accès au singleton
	 * @return SdzSingleton
	 */
	public static SdzSingleton getInstance(){
		if(single == null)
			single = new SdzSingleton();
		
		return single;
	}
	
	/**
	 * Accesseur 
	 * @return
	 */
	public String getName(){
		return this.name;
	}
}


Quoi, c'est ça ton singleton ?

Oui, ce n'est pas que je manquais d'inspiration, c'est juste qu'avec une classe toute simple, on comprend mieux les choses...

Et voici notre classe de test :

Code : Java
1
2
3
4
5
6
7
8
9
package com.sdz.connection;

public class TestSingleton {

	public static void main(String[] args) {
		for(int i = 1; i < 4; i++)
			System.out.println("Appel N° " + i + " : " + SdzSingleton.getInstance().getName());
	}
}


Ce qui nous donne :

Image utilisateur


La politique du singleton est toujours bonne. Maintenant, je vais vous poser une question : quand croyez-vous que la création d'une instance soit la plus judicieuse ?

Qu'est-ce que tu veux dire par là ?

C'est simple : ici, nous avons exécuté notre code et l'instance est créée lorsque qu'on la demande pour la première fois ! C'est le principal problème que pose le singleton et le multi-threading : la première instance... Une fois celle-ci créée, il y a moins de problème.

Parce que tu vois un autre endroit où créer l'instance, toi ?

Oui, au chargement de la classe par la JVM et ceci se fait en instanciant notre singleton à sa déclaration dans la classe, soit, comme ceci :

Code : Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package com.sdz.connection;

public class SdzSingleton {

	/**
	 * Le singleton
	 */
	private static SdzSingleton single = new SdzSingleton();
	/**
	 * Variable d'instance
	 */
	private String name = "";
	
	/**
	 * Constructeur privé
	 */
	private SdzSingleton(){
		this.name = "Mon singleton";
		System.out.println("\n\t\tCREATION DE L'INSTANCE ! ! !");
	}
	
	/**
	 * Méthode d'accès au singleton
	 * @return SdzSingleton
	 */
	public static SdzSingleton getInstance(){
		return single;
	}
	
	/**
	 * Accesseur 
	 * @return
	 */
	public String getName(){
		return this.name;
	}
}


Avec ce code, c'est la machine virtuelle qui va se charger de charger l'instance du singleton, bien avant que n'importe quel thread vienne taquiner la méthode getInstance() ... :magicien:

Il y a une autre méthode permettant de faire ceci, mais elle ne fonctionne parfaitement que depuis le JDK 1.5...
On appelle cette méthode : "le verrouillage à double vérification".
Cette méthode consiste à utiliser le mot clé volatile combiné au mot clé synchronized.
Pour les ZérOs qui l'ignorent, déclarer une variable volatile permet de s'assurer un accès ordonné des threads à une variable (plusieurs threads peuvent accéder à cette variable), Ceci marque le premier point de verrouillage.
Ensuite, la double vérification s'effectuera dans la méthode getInstance() , on synchronise UNIQUEMENT lorsque le singleton n'est pas créé.

Voici ce que ça nous donne :

Code : Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package com.sdz.connection;

public class SdzSingleton {

	/**
	 * Le singleton
	 */
	private volatile static SdzSingleton single;
	/**
	 * Variable d'instance
	 */
	private String name = "";
	
	/**
	 * Constructeur privé
	 */
	private SdzSingleton(){
		this.name = "Mon singleton";
		System.out.println("\n\t\tCREATION DE L'INSTANCE ! ! !");
	}
	
	/**
	 * Méthode d'accès au singleton
	 * @return SdzSingleton
	 */
	public static SdzSingleton getInstance(){
		if(single == null){
			synchronized(SdzSingleton.class){
				if(single == null)
					single = new SdzSingleton();
			}
		}
		
		return single;
	}
	
	/**
	 * Accesseur 
	 * @return
	 */
	public String getName(){
		return this.name;
	}
}


Voilà : vous êtes désormais incollables sur le singleton !
Je pense qu'après tout ça, un QCM s'impose...

Q.C.M.

Comment s'appelle le pattern permettant de n'avoir qu'une instance d'une classe à la fois ?
Cette classe implémente-t-elle ce pattern ?


Code : Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
package com.sdz.connection;

public class Single {

	private Single(){}
	
	public static Single getInstance(){
		return new Single();
	}	
}
Est-il possible de synchroniser la méthode qui retourne l'unique instance d'une classe ?
Qu'est-ce qui pourrait avoir le même effet que la synchronisation de la méthode retournant une instance unique ?

Statistiques de réponses au QCM

Bon : vous devez vous rendre compte que travailler avec JDBC n'a rien de compliqué, en définitive !
Je vois dans vos regards que vous avez perdu le côté mystifié de JDBC, maintenant ; vous devriez être à même d'utiliser cette API dans vos programmes Java.

Tiens, avec ce que nous venons de voir, ça me donne une idée de TP... :pirate:
Chapitre précédent Sommaire Chapitre suivant

Partager

7 commentaires pour "N'avoir qu'une instance de sa connexion"
Note moyenne : 3.34 / 4 (178 votes)
Pseudo Commentaire
Hors ligne softdounia # Posté le 15/08/2009 à 16:20:58
A
Avatar

Ville : Alger
Pays : Algérie
Études : ETS Montréal

merci. 19.5/20 :-°

Les 40e rugissants et les 50e hurlants. o_O --> Brainstorming --> CamelCase
Image utilisateur

-------------
"je crois qu'on ne peut mieux vivre qu'en chercant à devenir meilleur, ni plus agréablement qu'en ayant pleine conscience de son amélioration"
Socrate >_<
-------------
les meilleurs peintres se sont fait dire qu’ils n’étaient pas bon, aujourd’hui ce sont des Dieux!
-------------
RTFM :-° l'expression anglaise d'argot Internet Read the fucking manual (« Lis le foutu manuel ») ;
-------------
Mon CV en ligne ici
 
Hors ligne niponitoss # Posté le 17/08/2009 à 18:06:45
Avatar

bjr,
je pense pas que la déclaration du constructeur soit superflue vu que java affecte un constructeur par defaut aux classes qui n'en ont pas donc l'instanciation de l'objet connection peut se faire a volonté
Hors ligne softdounia # Posté le 11/06/2011 à 19:58:19
A
Avatar

Ville : Alger
Pays : Algérie
Études : ETS Montréal

une variable de connexion comme Singleton => sa fait pas un peut goulot d’étranglement

=> on ma dit que ces pas conseiller de faire sa. ??? (donc pas bon exemple)

Les 40e rugissants et les 50e hurlants. o_O --> Brainstorming --> CamelCase
Image utilisateur

-------------
"je crois qu'on ne peut mieux vivre qu'en chercant à devenir meilleur, ni plus agréablement qu'en ayant pleine conscience de son amélioration"
Socrate >_<
-------------
les meilleurs peintres se sont fait dire qu’ils n’étaient pas bon, aujourd’hui ce sont des Dieux!
-------------
RTFM :-° l'expression anglaise d'argot Internet Read the fucking manual (« Lis le foutu manuel ») ;
-------------
Mon CV en ligne ici
 
Connecté hedi07 # Posté le 28/08/2011 à 12:39:25
Avatar

Ville : Annemasse
Pays : France métropolitaine

softdounia => on peut utiliser un pool de connexion, ça réparti la charge. Mais c'est plus dur à faire.

Par contre, le dernier exemple avec la double vérification, il n'y a presque aucune explication : pourquoi un double check, pour synchornized sur le .class, pourquoi deux vérification si la connexion est bien nulle et quelle est l'utilité du mot clef volatile dans cette situation...

ça fait beaucoup à expliquer je pense et c'est peut être pas facile de le faire, ce n'est pas non plus de concepts triviaux. Mais je trouvais les explication bonnes tout au long du tuto, et pis là, d'un coup tu nous dit un truc comme "c'est comme ça" :o .

Hedi

tout vient a point a qui sait attendre. Image utilisateur

 
Hors ligne talented # Posté le 09/02/2012 à 21:21:00

Salut Cysboy,

Un très bon tuto! Merci

En effet, j'ai deux questions:

1- Ou est l'instruction qui charge le driver dans la mémoire,càd:
Class.forName("com.My......")?

2- Dans la fonction main() de la classe SdzConnection, lorsque je tape:
SdzConnection ct = new SdzConnection();
Normalement, ça ne devra pas fonctionner car le constructeur est privé ... mais Par magie l'IDE me permet de le faire !!!
Avez-vous une explication?

Merci une autre fois!

Voir tous les commentaires