Comment puis-je implémenter un gestionnaire DAO à l’aide de JDBC et de pools de connexions?

Mon problème est le suivant. J’ai besoin d’une classe qui fonctionne comme un point unique vers une connexion de firebase database dans un système Web, afin d’éviter d’avoir un utilisateur avec deux connexions ouvertes. J’ai besoin que ce soit 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 des DAO. Et pour le rendre meilleur, il devrait également utiliser le regroupement de connexions! Que devrais-je faire?

    Vous devrez implémenter un gestionnaire DAO . J’ai repris l’idée principale de ce site Web , mais j’ai créé ma propre implémentation qui résout quelques problèmes.

    Étape 1: regroupement des connexions

    Tout d’abord, vous devrez configurer un pool de connexion . Un pool de connexions est bien un pool de connexions. Lorsque votre application est exécutée, le pool de connexions démarrera un certain nombre de connexions. Cela permet d’éviter de créer des connexions au moment de l’exécution, car l’opération est coûteuse. Ce guide n’a pas pour but d’expliquer comment en configurer un, consultez donc la page.

    Pour mémoire, je vais utiliser Java comme langue et Glassfish comme serveur.

    Étape 2: connectez-vous à la firebase database

    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 chic.

     public class DAOManager { public DAOManager() throws Exception { try { InitialContext ctx = new InitialContext(); this.src = (DataSource)ctx.lookup("jndi/MYSQL"); //The ssortingng 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 sophistiquée, mais ce sera la base de ce que nous allons faire. Donc, en faisant ceci:

     DAOManager mngr = new DAOManager(); mngr.open(); mngr.close(); 

    devrait ouvrir et fermer votre connexion à la firebase database dans un object.

    Étape 3: Faites un seul point!

    Quoi, maintenant, si on faisait ça?

     DAOManager mngr1 = new DAOManager(); DAOManager mngr2 = new DAOManager(); mngr1.open(); mngr2.open(); 

    Certains pourraient argumenter, “pourquoi dans le monde voudriez-vous faire cela?” . Mais alors vous ne savez jamais ce qu’un programmeur va faire. Même dans ce cas, le programmeur peut falsifier la fermeture d’une connexion avant d’en ouvrir une nouvelle. De plus, cela représente un gaspillage de ressources pour l’application. Arrêtez-vous ici si vous voulez réellement avoir deux connexions ouvertes ou plus, ce sera une implémentation pour une connexion par utilisateur.

    Pour en faire un point unique, nous devrons convertir cette classe en singleton . Un singleton est un modèle de conception qui nous permet d’avoir une seule et même instance d’un object donné. Alors faisons-en un singleton!

    • Nous devons convertir notre constructeur public en un constructeur privé. Nous devons seulement donner une instance à celui qui l’appelle. Le DAOManager devient alors une usine!
    • Nous devons également append une nouvelle classe private qui stockera un singleton.
    • Parallèlement à cela, nous avons également besoin d’une méthode getInstance() qui nous donnera une instance singleton que nous pouvons appeler.

    Voyons comment cela 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 instancie un DAOManager . Très bien, nous avons créé un seul point d’access!

    Mais singleton est un anti-modèle pour 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. Ceci est juste un moyen d’implémenter cette solution, si vous avez d’autres moyens de le suggérer.

    Étape 4: Mais il y a quelque chose qui ne va pas …

    Oui, en effet il y en a. Un singleton ne créera qu’une seule instance pour toute l’application! Et ceci est faux à plusieurs niveaux, surtout si nous avons un système Web où notre application sera multithread ! Comment pouvons-nous résoudre ce problème, alors?

    Java fournit une classe nommée ThreadLocal . Une variable ThreadLocal aura une instance par thread. Hé, ça résout notre problème! Pour en savoir plus sur son fonctionnement , vous devrez comprendre son objective pour pouvoir 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 INSTANCE; static { ThreadLocal dm; try { dm = new ThreadLocal(){ @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 ça

     catch(Exception e) { return null; } 

    mais initialValue() ne peut pas lancer une exception. Oh, valeur initialValue() vous voulez dire? Cette méthode nous indiquera quelle valeur la variable ThreadLocal tiendra. 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. Nous devrions donc en créer au moins deux.

    Un DAO, en abrégé “Data Access Object”, est un modèle de conception qui confie la gestion des opérations de firebase database à une classe représentant une certaine table.

    Pour utiliser notre DAOManager plus efficacement, nous définirons un GenericDAO , un DAO abstrait qui contiendra les opérations courantes entre tous les DAO.

     public abstract class GenericDAO { public abstract int count() throws SQLException; //Protected protected final Ssortingng tableName; protected Connection con; protected GenericDAO(Connection con, Ssortingng tableName) { this.tableName = tableName; this.con = con; } } 

    Pour l’instant, cela suffira. Créons des DAO. Supposons que nous ayons deux POJO: le First et le Second , tous deux avec juste un champ Ssortingng nommé data et ses getters et setters.

     public class FirstDAO extends GenericDAO { public FirstDAO(Connection con) { super(con, TABLENAME); } @Override public int count() throws SQLException { Ssortingng 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 Ssortingng TABLENAME = "FIRST"; } 

    SecondDAO aura plus ou moins la même structure, il suffit de TABLENAME par "SECOND" .

    Étape 6: Faire du gestionnaire une usine

    DAOManager non seulement servir de sharepoint connexion unique. En fait, DAOManager devrait répondre à cette question:

    Qui est responsable de la gestion des connexions à la firebase database?

    Les DAO individuels ne doivent pas les gérer, mais DAOManager . Nous avons partiellement répondu à la question, mais nous ne devrions maintenant laisser personne gérer d’autres connexions à la firebase database, pas même les DAO. Mais les DAO ont besoin d’une connexion à la firebase database! Qui devrait le fournir? DAOManager effet! Ce que nous devrions faire, c’est créer une méthode d’usine dans DAOManager . Non seulement cela, mais DAOManager fournira également la connexion actuelle!

    Factory est un modèle de conception qui nous permettra de créer des instances d’une certaine super-classe, sans savoir exactement quelle classe enfant sera renvoyée.

    Commençons par créer une enum répertoriant nos tables.

     public enum Table { FIRST, SECOND } 

    Et maintenant, la méthode d’usine à l’intérieur de 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 sums 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 élégant et facile à lire? Pas seulement cela, mais lorsque vous appelez close() , vous fermez chaque connexion utilisée par les DAO. Mais comment?! Eh bien, ils partagent la même connexion, donc c’est juste naturel.

    Étape 8: Ajuster notre classe

    Nous pouvons 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() partir de la Connection afin que vous puissiez gérer plus efficacement vos transactions. Ce que j’ai également fait est qu’au lieu de simplement tenir une Connection , DAOManager contient également un PreparedStatement et un ResultSet . Ainsi, lorsque vous appelez close() il ferme également les deux. Un moyen rapide de clore les déclarations et les jeux de résultats!

    J’espère que ce guide pourra vous être utile dans votre prochain projet!

    Je pense que si vous voulez créer un motif DAO simple en langage JDBC simple, vous devriez le garder simple:

      public List listCustomers() { List 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 = dao.listCustomers(); 

    Notez que j’utilise try avec des ressources et que ce code est sans danger pour les fuites de connexion, qu’il soit propre et simple. Vous ne voulez probablement pas suivre le modèle DAO complet avec des Factorys, des interfaces et tout ce que la plomberie ne permet pas toujours. append de la valeur réelle.

    Je ne pense pas que ce soit une bonne idée d’utiliser ThreadLocals. Bad utilisé comme dans la réponse acceptée est une source de fuites de chargeur de classe.

    Rappelez-vous TOUJOURS de fermer vos ressources (instructions, ensembles de résultats, connexions) dans un bloc try final ou en utilisant try avec des ressources