Comment implémenter un gestionnaire DAO à l'aide de JDBC et de pools de connexion?
Mon problème est le suivant. J'ai besoin d'une classe qui fonctionne comme un point unique pour une connexion de base de données dans un système web, donc pour éviter d'avoir un utilisateur avec deux connexions ouvertes. J'en ai besoin pour être aussi optimal que possible et il devrait gérer chaque transaction dans le système. En d'autres termes, seule cette classe devrait pouvoir instancier les Dao. Et pour le rendre meilleur, il devrait également utiliser la mise en commun des connexions! Que devrais-je faire?
2 réponses
Vous devrez implémenter un gestionnaire Dao . J'ai pris l'idée principale de Ce site web , mais j'ai fait ma propre implémentation qui résout quelques problèmes.
Étape 1: regroupement de connexions
Tout d'abord, vous devrez configurer un pool de connexions . Un pool de connexions est, ainsi, un pool de connexions. Lorsque votre application s'exécute, le pool de connexions démarre un certain nombre de connexions, ceci pour éviter de créer des connexions dans d'exécution puisque c'est une opération coûteuse. Ce guide n'est pas destiné à expliquer comment en configurer un, alors allez regarder autour de cela.
Pour mémoire, je vais utiliser Java comme langue et Glassfish {[56] } comme serveur.
Étape 2: Se connecter à la base de données
Commençons par créer une classe DAOManager
. Donnons-lui des méthodes pour ouvrir et fermer une connexion en cours d'exécution. Rien de trop de fantaisie.
public class DAOManager {
public DAOManager() throws Exception {
try
{
InitialContext ctx = new InitialContext();
this.src = (DataSource)ctx.lookup("jndi/MYSQL"); //The string should be the same name you're giving to your JNDI in Glassfish.
}
catch(Exception e) { throw e; }
}
public void open() throws SQLException {
try
{
if(this.con==null || !this.con.isOpen())
this.con = src.getConnection();
}
catch(SQLException e) { throw e; }
}
public void close() throws SQLException {
try
{
if(this.con!=null && this.con.isOpen())
this.con.close();
}
catch(SQLException e) { throw e; }
}
//Private
private DataSource src;
private Connection con;
}
Ce n'est pas une classe très chic, mais ce sera la base de ce que nous allons faire. Donc, faire ceci:
DAOManager mngr = new DAOManager();
mngr.open();
mngr.close();
Doit ouvrir et fermer votre connexion à la base de données dans un objet.
Étape 3: Faites-en un seul point!
Et maintenant, si on faisait ça?
DAOManager mngr1 = new DAOManager();
DAOManager mngr2 = new DAOManager();
mngr1.open();
mngr2.open();
Certains pourraient argumenter, " pourquoi dans le monde feriez-vous cela?". Mais alors, vous ne savez jamais ce qu'un programmeur ne. Même alors, le programmeur peut falsifier de fermer une connexion avant d'en ouvrir une nouvelle. De plus, c'est un gaspillage de ressources pour le application. arrêtez ici si vous voulez réellement avoir deux ou plusieurs connexions ouvertes, ce sera une implémentation pour une connexion par utilisateur.
, afin d'en faire un seul point, nous devrons convertir cette classe dans un singleton. Un singleton est un modèle de conception qui nous permet d'avoir une et une seule instance d'un objet. Alors, faisons-en un singleton!
- , Nous devons convertir notre
public
constructeur en privé. Nous devons seulement donner une instance pour celui qui l'appelle. LeDAOManager
devient alors une usine! - nous devons également ajouter une nouvelle classe
private
qui va réellement stocker un singleton. - parallèlement à tout cela, nous avons également besoin d'une méthode
getInstance()
qui nous donnera une instance singleton que nous pouvons appeler.
Voyons comment il est implémenté.
public class DAOManager {
public static DAOManager getInstance() {
return DAOManagerSingleton.INSTANCE;
}
public void open() throws SQLException {
try
{
if(this.con==null || !this.con.isOpen())
this.con = src.getConnection();
}
catch(SQLException e) { throw e; }
}
public void close() throws SQLException {
try
{
if(this.con!=null && this.con.isOpen())
this.con.close();
}
catch(SQLException e) { throw e; }
}
//Private
private DataSource src;
private Connection con;
private DAOManager() throws Exception {
try
{
InitialContext ctx = new InitialContext();
this.src = (DataSource)ctx.lookup("jndi/MYSQL");
}
catch(Exception e) { throw e; }
}
private static class DAOManagerSingleton {
public static final DAOManager INSTANCE;
static
{
DAOManager dm;
try
{
dm = new DAOManager();
}
catch(Exception e)
dm = null;
INSTANCE = dm;
}
}
}
Lorsque l'application démarre, chaque fois que quelqu'un a besoin d'un singleton, le système va instancier un DAOManager
. Assez soigné, nous avons créé un seul accès point!
Mais singleton est un antipattern parce que des raisons! Je sais que certaines personnes n'aimeront pas singleton. Cependant, il résout le problème (et a résolu le mien) assez décemment. C'est juste une façon d'implémenter cette solution, si vous avez d'autres façons de Le suggérer.
Étape 4: mais il y a quelque chose qui ne va pas...
Oui, en effet. un singleton ne créera QU'une seule instance pour toute l'application! et c'est faux à plusieurs niveaux, surtout si nous avons un système web où notre demande sera multithread! Comment Pouvons-nous résoudre cela, alors?
Java fournit une classe nommée ThreadLocal
. Une variable ThreadLocal
aura une instance par thread. Hey, il résout notre problème! en savoir plus sur la façon dont cela fonctionne , vous aurez besoin de comprendre son but afin que nous puissions continuer.
Faisons notre INSTANCE
ThreadLocal
alors. Modifiez la classe de cette façon:
public class DAOManager {
public static DAOManager getInstance() {
return DAOManagerSingleton.INSTANCE.get();
}
public void open() throws SQLException {
try
{
if(this.con==null || !this.con.isOpen())
this.con = src.getConnection();
}
catch(SQLException e) { throw e; }
}
public void close() throws SQLException {
try
{
if(this.con!=null && this.con.isOpen())
this.con.close();
}
catch(SQLException e) { throw e; }
}
//Private
private DataSource src;
private Connection con;
private DAOManager() throws Exception {
try
{
InitialContext ctx = new InitialContext();
this.src = (DataSource)ctx.lookup("jndi/MYSQL");
}
catch(Exception e) { throw e; }
}
private static class DAOManagerSingleton {
public static final ThreadLocal<DAOManager> INSTANCE;
static
{
ThreadLocal<DAOManager> dm;
try
{
dm = new ThreadLocal<DAOManager>(){
@Override
protected DAOManager initialValue() {
try
{
return new DAOManager();
}
catch(Exception e)
{
return null;
}
}
};
}
catch(Exception e)
dm = null;
INSTANCE = dm;
}
}
}
J'aimerais sérieusement ne pas faire ce
catch(Exception e)
{
return null;
}
Mais initialValue()
ne peut pas lancer d'exception. Oh, initialValue()
tu veux dire? Cette méthode nous dira quelle valeur tiendra la variable ThreadLocal
. Fondamentalement, nous l'initialisons. Donc, grâce à cela, nous pouvons maintenant avoir une instance par thread.
Étape 5: Créer un DAO
Un DAOManager
n'est rien sans un DAO. Donc, nous devrions au moins en créer quelques-uns.
Un DAO, abréviation de "Data Access Object" est un modèle de conception qui donne la responsabilité de gérer opérations de base de données à une classe représentant une certaine table.
Afin d'utiliser notre DAOManager
plus efficacement, nous allons définir un GenericDAO
, qui est un DAO abstrait qui tiendra les opérations communes entre tous les Dao.
public abstract class GenericDAO<T> {
public abstract int count() throws SQLException;
//Protected
protected final String tableName;
protected Connection con;
protected GenericDAO(Connection con, String tableName) {
this.tableName = tableName;
this.con = con;
}
}
Pour l'instant, ça suffira. Nous allons créer des DAOs. Supposons que nous ayons deux POJOs: First
et Second
, les deux avec juste un champ String
nommé data
et ses getters et setters.
public class FirstDAO extends GenericDAO<First> {
public FirstDAO(Connection con) {
super(con, TABLENAME);
}
@Override
public int count() throws SQLException {
String query = "SELECT COUNT(*) AS count FROM "+this.tableName;
PreparedStatement counter;
try
{
counter = this.con.PrepareStatement(query);
ResultSet res = counter.executeQuery();
res.next();
return res.getInt("count");
}
catch(SQLException e){ throw e; }
}
//Private
private final static String TABLENAME = "FIRST";
}
SecondDAO
aura plus ou moins le même structure, juste en changeant TABLENAME
à "SECOND"
.
Étape 6: Faire du Gestionnaire une usine
DAOManager
non seulement devrait servir le but de servir de point de connexion unique. En fait, {[12] } devrait répondre à cette question:
Qui est responsable de la gestion des connexions à la base de données?
Les Dao individuels ne devraient pas les gérer, mais DAOManager
. Nous avons répondu partiellement à la question, mais maintenant nous ne devrions laisser personne gérer d'autres connexions à la base de données, pas même les Dao. Mais, les DAO ont besoin d'une connexion à la base de données! Qui devrait le fournir? DAOManager
en effet! Ce que nous devrions faire est de faire une méthode d'usine à l'intérieur DAOManager
. Non seulement cela, mais DAOManager
leur remettra également la connexion actuelle!
Factory est un modèle de conception qui nous permettra de créer des instances d'une certaine superclasse, sans savoir exactement quelle classe enfant sera retournée.
Tout d'abord, créons un enum
liste de nos table.
public enum Table { FIRST, SECOND }
Et maintenant, la méthode d'usine à l'intérieur DAOManager
:
public GenericDAO getDAO(Table t) throws SQLException
{
try
{
if(this.con == null || this.con.isClosed()) //Let's ensure our connection is open
this.open();
}
catch(SQLException e){ throw e; }
switch(t)
{
case FIRST:
return new FirstDAO(this.con);
case SECOND:
return new SecondDAO(this.con);
default:
throw new SQLException("Trying to link to an unexistant table.");
}
}
Étape 7: tout mettre ensemble
Nous sommes prêts à partir maintenant. Essayez le code suivant:
DAOManager dao = DAOManager.getInstance();
FirstDAO fDao = (FirstDAO)dao.getDAO(Table.FIRST);
SecondDAO sDao = (SecondDAO)dao.getDAO(Table.SECOND);
System.out.println(fDao.count());
System.out.println(sDao.count());
dao.close();
N'est-ce pas chic et facile à lire? Non seulement cela, mais lorsque vous appelez close()
, vous fermez chaque connexion le DAOs utilisez. , Mais comment?! Eh bien, ils partagent la même connexion, donc c'est naturel.
Étape 8: affiner notre classe
On peut faire plusieurs choses à partir d'ici. Pour vous assurer que les connexions sont fermées et renvoyées au pool, procédez comme suit dans DAOManager
:
@Override
protected void finalize()
{
try{ this.close(); }
finally{ super.finalize(); }
}
Vous pouvez également implémenter des méthodes qui encapsulent setAutoCommit()
, commit()
et rollback()
De La Connection
afin que vous puissiez avoir une meilleure gestion de vos transactions. Ce que j'ai aussi fait est, au lieu de simplement tenir un Connection
, DAOManager
contient également un PreparedStatement
et un ResultSet
. Donc, en appelant close()
, Il ferme également les deux. Un moyen rapide de clôture des déclarations et des ensembles de résultats!
J'espère ce guide peut vous être utile dans votre prochain projet!
Je pense que si vous voulez faire un modèle DAO simple dans JDBC simple, vous devriez rester simple:
public List<Customer> listCustomers() {
List<Customer> list = new ArrayList<>();
try (Connection conn = getConnection();
Statement s = conn.createStatement();
ResultSet rs = s.executeQuery("select * from customers")) {
while (rs.next()) {
list.add(processRow(rs));
}
return list;
} catch (SQLException e) {
throw new RuntimeException(e.getMessage(), e); //or your exceptions
}
}
Vous pouvez suivre ce modèle dans une classe appelée par exemple CustomersDao ou CustomerManager, et vous pouvez l'appeler avec un simple
CustomersDao dao = new CustomersDao();
List<Customers> customers = dao.listCustomers();
Notez que j'utilise try avec des ressources et que ce code est sûr pour les fuites de connexions, propre et simple, vous ne voulez probablement pas suivre le modèle DAO complet avec Factorys, interfaces et toute cette plomberie que dans de nombreux cas n'ajoutez pas de valeur réelle.
Je ne pense pas que ce soit une bonne idée d'utiliser ThreadLocals, mauvais utilisé comme dans la réponse acceptée est une source de fuites de classloader
Rappelez-vous toujours fermer vos ressources (instructions, ResultSets, connexions) dans un bloc try finally ou en utilisant try with resources