如何使用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: First
和Second
,只有一个名为data
的String
字段及其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
还拥有PreparedStatement
和ResultSet
,而不仅仅是持有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关闭资源(语句,结果集,连接)