如何使用JDBC和连接池实现DAO管理器?

我的问题如下。 我需要一个类作为Web系统中数据库连接的单点,以避免一个用户有两个打开的连接。 我需要它尽可能优化,它应该管理系统中的每个事务。 换句话说,只有该类应该能够实例化DAO。 为了使它更好,它还应该使用连接池! 我该怎么办?

您需要实现DAO Manager 。 我从这个网站上获取了主要想法,但是我做了自己的实现,解决了一些问题。

第1步:连接池

首先,您必须配置连接池 。 连接池就是一个连接池。 当您的应用程序运行时,连接池将启动一定数量的连接,这样做是为了避免在运行时创建连接,因为这是一项昂贵的操作。 本指南并不是要解释如何配置一个,所以去看看。

为了记录,我将使用Java作为我的语言,使用Glassfish作为我的服务器。

第2步:连接到数据库

让我们从创建DAOManager类开始。 让我们给它在运行时打开和关闭连接的方法。 没什么太花哨的。

 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; } 

这不是一个非常花哨的课程,但它将成为我们将要做的事情的基础。 所以,这样做:

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

应该打开和关闭与对象中数据库的连接。

第3步:让它成为一个点!

现在,如果我们这样做了什么?

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

有人可能会争辩说, “为什么你会这样做呢?” 。 但是你永远不知道程序员会做什么。 即使这样,程序员也可能会在打开新连接之前关闭连接。 另外,这会浪费应用程序的资源。 如果你真的想要有两个或更多的开放连接,请停在这里,这将是每个用户一个连接的实现。

为了使它成为单一点,我们必须将此类转换为单个类。 单例是一种设计模式,它允许我们拥有任何给定对象的一个​​且只有一个实例。 所以,让它成为一个单身人士!

  • 我们必须将我们的public构造函数转换为私有构造函数。 我们必须只给那个叫它的人一个实例。 然后DAOManager成为工厂!
  • 我们还必须添加一个实际存储单例的新private类。
  • 除此之外,我们还需要一个getInstance()方法,它将为我们提供一个可以调用的单例实例。

让我们看看它是如何实现的。

 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; } } } 

当应用程序启动时,只要有人需要单例,系统就会实例化一个DAOManager 。 非常简洁,我们创建了一个单一的接入点!

但是单身是反模式的原因! 我知道有些人不会喜欢单身人士。 然而,它解决了这个问题(并且已经解决了我的问题)相当不错。 这只是实现此解决方案的一种方式,如果您有其他方式,欢迎您这样做。

第4步:但是出了点问题……

是的,的确有。 单例将为整个应用程序创建一个实例! 这在许多层面都是错误的,特别是如果我们有一个网络系统,我们的应用程序将是multithreading的 ! 那么我们如何解决这个问题呢?

Java提供了一个名为ThreadLocal的类。 ThreadLocal变量每个线程有一个实例。 嘿,它解决了我们的问题! 详细了解它的工作原理 ,您需要了解其目的,以便我们继续。

让我们来制作我们的INSTANCE ThreadLocal 。 以这种方式修改类:

 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; } } } 

我真的很想不要这样做

 catch(Exception e) { return null; } 

但是initialValue()不能抛出exception。 哦, initialValue()你的意思是? 此方法将告诉我们ThreadLocal变量将保持什么值。 基本上我们正在初始化它。 所以,多亏了这一点,我们现在每个线程可以有一个实例。

第5步:创建DAO

如果没有DAO, DAOManager就没什么DAOManager 。 所以我们至少应该创建几个。

DAO是“数据访问对象”的缩写,是一种设计模式,它将管理数据库操作的责任权交给代表某个表的类。

为了更有效地使用我们的DAOManager ,我们将定义一个GenericDAO ,它是一个抽象的DAO,它将保存所有DAO之间的公共操作。

 public abstract class GenericDAO { 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; } } 

现在,这就足够了。 让我们创建一些DAO。 假设我们有两个POJO: FirstSecond ,只有一个名为dataString字段及其getter和setter。

 public class FirstDAO extends GenericDAO { 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将具有或多或少相同的结构,只需将TABLENAME更改为"SECOND"

第6步:让经理成为工厂

DAOManager不仅应该服务于作为单个连接点的目的。 实际上, DAOManager应该回答这个问题:

谁负责管理与数据库的连接?

各个DAO不应该管理它们,而是DAOManager 。 我们已经部分回答了这个问题,但现在我们不应该让任何人管理与数据库的其他连接,甚至不管DAO。 但是,DAO需要连接到数据库! 谁应该提供它? DAOManager确实! 我们应该做的是在DAOManager创建一个工厂方法。 不仅如此, DAOManager还将提供当前连接!

Factory是一种设计模式,它允许我们创建某个超类的实例,而不确切知道将返回哪个子类。

首先,让我们创建一个列出表格的enum

 public enum Table { FIRST, SECOND } 

而现在, 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."); } } 

第7步:把所有东西放在一起

我们现在很高兴。 请尝试以下代码:

 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(); 

是不是看上去很容易? 不仅如此,当您调用close() ,您关闭DAO正在使用的每个连接但是怎么样?! 好吧,他们共享相同的连接,所以这很自然。

第8步:微调我们的课程

我们可以从这里做几件事。 要确保关闭连接并返回池,请在DAOManager执行以下DAOManager

 @Override protected void finalize() { try{ this.close(); } finally{ super.finalize(); } } 

您还可以实现从Connection封装setAutoCommit()commit()rollback() ,以便更好地处理事务。 我还做的是, DAOManager还拥有PreparedStatementResultSet ,而不仅仅是持有Connection 。 因此,当调用close()它也会关闭。 关闭语句和结果集的快捷方法!

我希望本指南在您的下一个项目中对您有用!

我认为如果你想在普通JDBC中做一个简单的DAO模式,你应该保持简单:

  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 } } 

您可以在名为CustomersDao或CustomerManager的类中遵循此模式,您可以使用简单的方法调用它

 CustomersDao dao = new CustomersDao(); List customers = dao.listCustomers(); 

请注意,我正在尝试使用资源,这个代码对于连接泄漏,干净和简单是安全的,您可能不希望使用Factorys,接口和所有那些在许多情况下没有的管道的完整DAO模式增加真正的价值。

我认为使用ThreadLocals不是一个好主意,在接受的答案中使用的Bad是类加载器泄漏的来源

请记住,在try finally块中或使用try with resources关闭资源(语句,结果集,连接)