http://www.ibm.com/developerworks/cn/java/l-connpoolproxy/index.html
级别: 初级
刘冬 (winter.lau@163.com), 珠海市创我科技发展有限公司软件工程师
2002 年 12 月 05 日
作者通过使用JAVA中的动态代理实现数据库连接池,使使用者可以以普通的jdbc连接的使用习惯来使用连接池。
数据库连接池在编写应用服务是经常需要用到的模块,太过频繁的连接数据库对服务性能来讲是一个瓶颈,使用缓冲池技术可以来消除这个瓶颈。我们可以在互联网上找到很多关于数据库连接池的源程序,但是都发现这样一个共同的问题:这些连接池的实现方法都不同程度地增加了与使用者之间的耦合度。很多的连接池都要求用户通过其规定的方法获取数据库的连接,这一点我们可以理解,毕竟目前所有的应用服务器取数据库连接的方式都是这种方式实现的。但是另外一个共同的问题是,它们同时不允许使用者显式的调用Connection.close()方法,而需要用其规定的一个方法来关闭连接。这种做法有两个缺点:
第一:改变了用户使用习惯,增加了用户的使用难度。
首先我们来看看一个正常的数据库操作过程:
java 代码
- int executeSQL(String sql) throws SQLException
- {
- Connection conn = getConnection();
- PreparedStatement ps = null;
- int res = 0;
- try{
- ps = conn.prepareStatement(sql);
- res = ps.executeUpdate();
- }finally{
- try{
- ps.close();
- }catch(Exception e){}
- try{
- conn.close();
- }catch(Exception e){}
- }
- return res;
- }
-
使用者在用完数据库连接后通常是直接调用连接的方法close来释放数据库资源,如果用我们前面提到的连接池的实现方法,那语句conn.close()将被某些特定的语句所替代。
第二:使连接池无法对之中的所有连接进行独占控制。由于连接池不允许用户直接调用连接的close方法,一旦使用者在使用的过程中由于习惯问题直接关闭了数据库连接,那么连接池将无法正常维护所有连接的状态,考虑连接池和应用由不同开发人员实现时这种问题更容易出现。
综合上面提到的两个问题,我们来讨论一下如何解决这两个要命的问题。
首先我们先设身处地的考虑一下用户是想怎么样来使用这个数据库连接池的。用户可以通过特定的方法来获取数据库的连接,同时这个连接的类型应该是标准的java.sql.Connection。用户在获取到这个数据库连接后可以对这个连接进行任意的操作,包括关闭连接等。
通过对用户使用的描述,怎样可以接管Connection.close方法就成了我们这篇文章的主题。
为了接管数据库连接的close方法,我们应该有一种类似于钩子的机制。例如在Windows编程中我们可以利用Hook API来实现对某个Windows API的接管。在JAVA中同样也有这样一个机制。JAVA提供了一个Proxy类和一个InvocationHandler,这两个类都在java.lang.reflect包中。我们先来看看SUN公司提供的文档是怎么描述这两个类的。
public interface InvocationHandler
InvocationHandler is the interface implemented by the invocation handler of a proxy instance.
Each proxy instance has an associated invocation handler.
When a method is invoked on a proxy instance,
the method invocation is encoded and dispatched to the invoke method of its invocation handler.
SUN的API文档中关于Proxy的描述很多,这里就不罗列出来。通过文档对接口InvocationHandler的描述我们可以看到当调用一个Proxy实例的方法时会触发Invocationhanlder的invoke方法。从JAVA的文档中我们也同时了解到这种动态代理机制只能接管接口的方法,而对一般的类无效,考虑到java.sql.Connection本身也是一个接口由此就找到了解决如何接管close方法的出路。
首先,我们先定义一个数据库连接池参数的类,定义了数据库的JDBC驱动程序类名,连接的URL以及用户名口令等等一些信息,该类是用于初始化连接池的参数,具体定义如下:
java 代码
- public class ConnectionParam implements Serializable
- {
- private String driver;
- private String url;
- private String user;
- private String password;
- private int minConnection = 0;
- private int maxConnection = 50;
- private long timeoutValue = 600000;
- private long waitTime = 30000;
其次是连接池的工厂类ConnectionFactory,通过该类来将一个连接池对象与一个名称对应起来,使用者通过该名称就可以获取指定的连接池对象,具体代码如下:
java 代码
-
-
-
-
- public class ConnectionFactory
- {
-
- static Hashtable connectionPools = null;
- static{
- connectionPools = new Hashtable(2,0.75F);
- }
-
-
-
-
-
-
- public static DataSource lookup(String dataSource)
- throws NameNotFoundException
- {
- Object ds = null;
- ds = connectionPools.get(dataSource);
- if(ds == null || !(ds instanceof DataSource))
- throw new NameNotFoundException(dataSource);
- return (DataSource)ds;
- }
-
-
-
-
-
-
-
-
-
-
-
- public static DataSource bind(String name, ConnectionParam param)
- throws NameAlreadyBoundException,ClassNotFoundException,
- IllegalAccessException,InstantiationException,SQLException
- {
- DataSourceImpl source = null;
- try{
- lookup(name);
- throw new NameAlreadyBoundException(name);
- }catch(NameNotFoundException e){
- source = new DataSourceImpl(param);
- source.initConnection();
- connectionPools.put(name, source);
- }
- return source;
- }
-
-
-
-
-
-
-
-
-
-
-
- public static DataSource rebind(String name, ConnectionParam param)
- throws NameAlreadyBoundException,ClassNotFoundException,
- IllegalAccessException,InstantiationException,SQLException
- {
- try{
- unbind(name);
- }catch(Exception e){}
- return bind(name, param);
- }
-
-
-
-
-
- public static void unbind(String name) throws NameNotFoundException
- {
- DataSource dataSource = lookup(name);
- if(dataSource instanceof DataSourceImpl){
- DataSourceImpl dsi = (DataSourceImpl)dataSource;
- try{
- dsi.stop();
- dsi.close();
- }catch(Exception e){
- }finally{
- dsi = null;
- }
- }
- connectionPools.remove(name);
- }
-
- }
ConnectionFactory主要提供了用户将将连接池绑定到一个具体的名称上以及取消绑定的操作。使用者只需要关心这两个类即可使用数据库连接池的功能。下面我们给出一段如何使用连接池的代码:
java 代码
- String name = "pool";
- String driver = " sun.jdbc.odbc.JdbcOdbcDriver ";
- String url = "jdbc:odbc:datasource";
- ConnectionParam param = new ConnectionParam(driver,url,null,null);
- param.setMinConnection(1);
- param.setMaxConnection(5);
- param.setTimeoutValue(20000);
- ConnectionFactory.bind(name, param);
- System.out.println("bind datasource ok.");
-
-
- DataSource ds = ConnectionFactory.lookup(name);
- try{
- for(int i=0;i<10;i++){
- Connection conn = ds.getConnection();
- try{
- testSQL(conn, sql);
- }finally{
- try{
- conn.close();
- }catch(Exception e){}
- }
- }
- }catch(Exception e){
- e.printStackTrace();
- }finally{
- ConnectionFactory.unbind(name);
- System.out.println("unbind datasource ok.");
- System.exit(0);
- }
-
从使用者的示例代码就可以看出,我们已经解决了常规连接池产生的两个问题。但是我们最最关心的是如何解决接管close方法的办法。接管工作主要在ConnectionFactory中的两句代码:
java 代码
- source = new DataSourceImpl(param);
- source.initConnection();
DataSourceImpl是一个实现了接口javax.sql.DataSource的类,该类维护着一个连接池的对象。由于该类是一个受保护的类,因此它暴露给使用者的方法只有接口DataSource中定义的方法,其他的所有方法对使用者来说都是不可视的。我们先来关心用户可访问的一个方法getConnection
java 代码
-
-
-
- public Connection getConnection(String user, String password) throws SQLException
- {
-
- Connection conn = getFreeConnection(0);
- if(conn == null){
-
-
- if(getConnectionCount() >= connParam.getMaxConnection())
- conn = getFreeConnection(connParam.getWaitTime());
- else{
- connParam.setUser(user);
- connParam.setPassword(password);
- Connection conn2 = DriverManager.getConnection(connParam.getUrl(),
- user, password);
-
- _Connection _conn = new _Connection(conn2,true);
- synchronized(conns){
- conns.add(_conn);
- }
- conn = _conn.getConnection();
- }
- }
- return conn;
- }
-
-
-
-
-
-
-
- protected synchronized Connection getFreeConnection(long nTimeout)
- throws SQLException
- {
- Connection conn = null;
- Iterator iter = conns.iterator();
- while(iter.hasNext()){
- _Connection _conn = (_Connection)iter.next();
- if(!_conn.isInUse()){
- conn = _conn.getConnection();
- _conn.setInUse(true);
- break;
- }
- }
- if(conn == null && nTimeout > 0){
-
- try{
- Thread.sleep(nTimeout);
- }catch(Exception e){}
- conn = getFreeConnection(0);
- if(conn == null)
- throw new SQLException("没有可用的数据库连接");
- }
- return conn;
- }
DataSourceImpl类中实现getConnection方法的跟正常的数据库连接池的逻辑是一致的,首先判断是否有空闲的连接,如果没有的话判断连接数是否已经超过最大连接数等等的一些逻辑。但是有一点不同的是通过DriverManager得到的数据库连接并不是及时返回的,而是通过一个叫_Connection的类中介一下,然后调用_Connection.getConnection返回的。如果我们没有通过一个中介也就是JAVA中的Proxy来接管要返回的接口对象,那么我们就没有办法截住Connection.close方法。
终于到了核心所在,我们先来看看_Connection是如何实现的,然后再介绍是客户端调用Connection.close方法时走的是怎样一个流程,为什么并没有真正的关闭连接。
java 代码
- **
- * 数据连接的自封装,屏蔽了close方法
- * @author Liudong
- */
- class _Connection implements InvocationHandler
- {
- private final static String CLOSE_METHOD_NAME = "close";
- private Connection conn = null;
-
- private boolean inUse = false;
-
- private long lastAccessTime = System.currentTimeMillis();
-
- _Connection(Connection conn, boolean inUse){
- this.conn = conn;
- this.inUse = inUse;
- }
-
-
-
-
- public Connection getConnection() {
-
- Connection conn2 = (Connection)Proxy.newProxyInstance(
- conn.getClass().getClassLoader(),
- conn.getClass().getInterfaces(),this);
- return conn2;
- }
-
-
-
-
- void close() throws SQLException{
-
- conn.close();
- }
-
-
-
-
- public boolean isInUse() {
- return inUse;
- }
-
-
-
- public Object invoke(Object proxy, Method m, Object[] args)
- throws Throwable
- {
- Object obj = null;
-
- if(CLOSE_METHOD_NAME.equals(m.getName()))
- setInUse(false);
- else
- obj = m.invoke(conn, args);
-
- lastAccessTime = System.currentTimeMillis();
- return obj;
- }
-
-
-
-
-
- public long getLastAccessTime() {
- return lastAccessTime;
- }
-
-
-
-
- public void setInUse(boolean inUse) {
- this.inUse = inUse;
- }
- }
一旦使用者调用所得到连接的close方法,由于用户的连接对象是经过接管后的对象,因此JAVA虚拟机会首先调用_Connection.invoke方法,在该方法中首先判断是否为close方法,如果不是则将代码转给真正的没有被接管的连接对象conn。否则的话只是简单的将该连接的状态设置为可用。到此您可能就明白了整个接管的过程,但是同时也有一个疑问:这样的话是不是这些已建立的连接就始终没有办法真正关闭?答案是可以的。我们来看看ConnectionFactory.unbind方法,该方法首先找到名字对应的连接池对象,然后关闭该连接池中的所有连接并删除掉连接池。在DataSourceImpl类中定义了一个close方法用来关闭所有的连接,详细代码如下:
java 代码
-
-
-
-
-
- public int close() throws SQLException
- {
- int cc = 0;
- SQLException excp = null;
- Iterator iter = conns.iterator();
- while(iter.hasNext()){
- try{
- ((_Connection)iter.next()).close();
- cc ++;
- }catch(Exception e){
- if(e instanceof SQLException)
- excp = (SQLException)e;
- }
- }
- if(excp != null)
- throw excp;
- return cc;
- }
该方法一一调用连接池中每个对象的close方法,这个close方法对应的是_Connection中对close的实现,在_Connection定义中关闭数据库连接的时候是直接调用没有经过接管的对象的关闭方法,因此该close方法真正的释放了数据库资源。
以上文字只是描述了接口方法的接管,具体一个实用的连接池模块还需要对空闲连接的监控并及时释放连接,详细的代码请参照附件。
分享到:
相关推荐
### Java动态代理实现数据库连接池 #### 背景与挑战 在开发应用程序时,数据库连接池是一个常用且重要的组成部分。频繁地建立和断开数据库连接不仅效率低下,还可能导致性能瓶颈。为了解决这一问题,引入了数据库...
本文将深入探讨如何使用Java中的动态代理来实现数据库连接池。 动态代理是Java中的一种高级特性,它允许我们在运行时创建具有特定接口实现的代理类。这种技术通常用于AOP(面向切面编程)或者为已有对象添加额外的...
"使用 JAVA 中的动态代理实现数据库连接池" 在编写应用服务时,数据库连接池是一个经常需要用到的模块,但是太过频繁的连接数据库对服务性能来说是一个瓶颈。使用缓冲池技术可以消除这个瓶颈。然而,现有的连接池...
总的来说,Java的动态代理、反射机制和数据库连接池是Java开发中不可或缺的工具和技术,它们极大地增强了Java应用程序的灵活性、可维护性和性能。学习并熟练掌握这些技术,对于提升开发效率和代码质量具有重要意义。
在自定义连接池中,我们可以结合Java动态代理,实现对每个数据库连接的智能管理。例如,当我们通过代理对象调用`connect()`方法时,代理对象会从连接池中获取一个可用的连接;调用完成后,代理对象自动将连接返回到...
1. **获取连接**:在代理方法执行前,从连接池中获取一个数据库连接。 2. **执行操作**:将获取到的连接传递给实际的目标方法,执行数据库操作。 3. **释放连接**:在目标方法执行完毕后,将连接归还给连接池。 ...
在本主题“代理模式之静态代理---数据库连接池对象实现原理”中,我们将探讨如何使用静态代理来实现数据库连接池。数据库连接池是现代应用中常用的优化手段,它可以高效地管理数据库连接,避免频繁地创建和关闭连接...
在数据库连接池中,我们可以使用动态代理来封装真实的数据库连接,使得每次对数据库的访问操作都通过代理对象完成。这样,我们可以在代理对象中添加额外的功能,如连接的获取、释放、监控等。 以下是使用动态代理...
在本主题中,我们将探讨如何使用Java的动态代理技术来实现一个更灵活、耦合度更低的数据库连接池。 首先,让我们了解动态代理的工作原理。在Java中,动态代理允许我们在运行时创建一个实现了指定接口的新类。这个新...
在Java开发中,有三种常见的数据库连接池实现:DBCP(BasicDataSource)、C3P0以及阿里开源的Druid。下面将详细介绍这三种数据库连接池的实现原理和特性。 1. **DBCP (BasicDataSource)** DBCP 是 Apache Commons ...
"程序设计模式文档.pdf"可能包含了关于在项目中使用的设计模式的详细解释,而"程序设计模式.ppt"可能是关于设计模式的演示文稿,可以帮助我们更深入地理解如何将这些模式应用到数据库连接池的实现中。"运行必看.txt...
在这个项目中,你使用Java实现了一个自定义的数据库连接池,应用了代理模式来优化连接管理和提高性能。下面我们将深入探讨这个主题。 首先,让我们了解什么是数据库连接池。在传统的数据库操作中,每当有新的数据库...
标题中提到的"各种数据库连接池",包括了c3p、DBCP和Proxool,这些都是Java环境下常见的数据库连接池实现: 1. **C3P0**:这是一个开源的JDBC连接池,它实现了数据源和JNDI绑定,支持JDBC3规范和JDBC2的标准扩展。...
数据库连接池的基本原理是预先在内存中创建一定数量的数据库连接,应用程序在需要时可以从池中获取连接,使用完毕后再归还,而不是每次操作都创建新的连接。这大大减少了创建和销毁连接的开销,提高了系统运行效率。...
数据库连接池在Oracle数据库中的实现是一项关键的技术优化策略,它主要解决了基于JDBC(Java Database Connectivity)的应用程序在处理数据库连接时的效率问题。在传统的数据库应用中,每次请求都需要建立一个新的...
Java 实现数据库连接池主要涉及两个关键点:减少使用者与连接池之间的耦合度和接管 `Connection.close()` 方法。为了实现这两个目标,我们可以利用 Java 的动态代理机制。 首先,了解数据库连接池的作用。连接池是...