`
mfcai
  • 浏览: 404466 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

数据库连接池技术的研究与实现

阅读更多
数据库连接池技术,它的原理是,在应用系统运行过程中,同时打开着一定数量的数据库连接,形成数据连接池,需要建立数据库连接时,只需从“缓冲池”中取出一个,使用完毕之后再放回去,完成某些SQL操作后,系统自动回收,以供其它用户(或进程)调用。现在大多数的WEB应用服务器如BEA的WebLogic和IBM的WebSphere等提供了连接池的机制.不过,比起配置应用服务器提供的数据库连接池,自己写一个数据库连接池更有成就感.

要自己动手写一个连接池,有许多功能上的考虑。
1)连接池的分配与释放
连接池的分配与释放,对系统的性能有很大的影响。合理的分配与释放,可以提高连接的复用度,从而降低建立新连接的开销,同时还可以加快用户的访问速度。
  对于连接的管理可使用空闲池。即把已经创建但尚未分配出去的连接按创建时间存放到一个空闲池中。每当用户请求一个连接时,系统首先检查空闲池内有没有空闲连接。如果有就把建立时间最长(通过容器的顺序存放实现)的那个连接分配给他(实际是先做连接是否有效的判断,如果可用就分配给用户,如不可用就把这个连接从空闲池删掉,重新检测空闲池是否还有连接);如果没有则检查当前所开连接池是否达到连接池所允许的最大连接数(maxConn),如果没有达到,就新建一个连接,如果已经达到,就等待一定的时间(timeout)。如果在等待的时间内有连接被释放出来就可以把这个连接分配给等待的用户,如果等待时间超过预定时间timeout,则返回空值(null)。系统对已经分配出去正在使用的连接只做计数,当使用完后再返还给空闲池。对于空闲连接的状态,可开辟专门的线程定时检测,这样会花费一定的系统开销,但可以保证较快的响应速度。也可采取不开辟专门线程,只是在分配前检测的方法。
2)连接池的配置与维护
连接池中到底应该放置多少连接,才能使系统的性能最佳?系统可采取设置最小连接数(minConn)和最大连接数(maxConn)来控制连接池中的连接。最小连接数是系统启动时连接池所创建的连接数。如果创建过多,则系统启动就慢,但创建后系统的响应速度会很快;如果创建过少,则系统启动的很快,响应起来却慢。这样,可以在开发时,设置较小的最小连接数,开发起来会快,而在系统实际使用时设置较大的,因为这样对访问客户来说速度会快些。最大连接数是连接池中允许连接的最大数目,具体设置多少,要看系统的访问量,可通过反复测试,找到最佳点。
3)并发问题
  为了使连接管理服务具有最大的通用性,必须考虑多线程环境,即并发问题。这个问题相对比较好解决,因为Java语言自身提供了对并发管理的支持,使用synchronized关键字即可确保线程是同步的。使用方法为直接在类方法前面加上synchronized关键字,如:
  public synchronized Connection getConnection()



一个ConnectionPool的实现
以下代码在jdk1.4以上版本编译通过
package test;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Date;
import java.util.Vector;

public class ConnectionPool {
 private final Vector pools = new Vector();

 private int clients;

 private final int maxClients=25;

 /**
  * 获取数据库连接 如果当前池中有可用连接,则将池中最后一个返回;若没有,则创建一个新的返回
  */
 public synchronized java.sql.Connection getConnection() {
  java.sql.Connection con = null;
  if (pools.size() > 0) {
   // Pick the first Connection in the Vector
   // to get round-robin usage
   con = (java.sql.Connection) pools.firstElement();
   pools.removeElementAt(0);
   try {
    if (con.isClosed()) {
     System.out
       .println("Removed bad connection from connection pool");
     // Try again recursively
     con = getConnection();
    }
   } catch (SQLException e) {
    System.out
      .println("Removed bad connection from  connection pool");
    // Try again recursively
    con = getConnection();
   }
  } else if (maxClients == 0 || clients < maxClients) {
   con = createConnection();
  }
  if (con != null) {
   clients++;
  }
  return con;
 }

 /*
  * 当对一个连接的请求不能被满足时,等待,直到pool中有空闲Connection
  */
 public synchronized java.sql.Connection getConnection(long timeout) {
  long startTime = new Date().getTime();
  java.sql.Connection con;
  while ((con = getConnection()) == null) {
   try {
    wait(timeout);
   } catch (InterruptedException e) {
   }
   if ((new Date().getTime() - startTime) >= timeout) {
    // Timeout has expired
    return null;
   }
  }
  return con;
 }

 /*
  * 生成一个数据库连接conn
  */
 public static Connection createConnection() {
  Connection conn = null;
  try {
   Class.forName("org.gjt.mm.mysql.Driver").newInstance();
   String url = "jdbc:mysql://localhost:3306/microerp";
   String user = "root";
   String password = "";
   conn = DriverManager.getConnection(url, user, password);
  } catch (InstantiationException e) {
   // TODO自动生成 catch 块
   e.printStackTrace();
  } catch (IllegalAccessException e) {
   // TODO自动生成 catch 块
   e.printStackTrace();
  } catch (ClassNotFoundException e) {
   // TODO自动生成 catch 块
   e.printStackTrace();
  } catch (SQLException e) {
   // TODO自动生成 catch 块
   e.printStackTrace();
  }
  return conn;
 }

 /**
  * 将使用完毕的数据库连接放回池中 若池中连接已经超过阈值,则关闭该连接;否则放回池中下次再使用
  */
 public synchronized void releaseConnection(Connection conn) {
  if (pools.size() >= maxClients)
   try {
    conn.close();
   } catch (SQLException e) {
    // TODO自动生成 catch 块
    e.printStackTrace();
   }
  else {
   pools.add(conn);
   clients--;
   notify();
  }
 }
 
 public synchronized void clear(){
        for(int i=0;i<pools.size();i++){
         Connection conn =(Connection)pools.get(i);
         try {
    conn.close();
   } catch (SQLException e) {
    // TODO自动生成 catch 块
    e.printStackTrace();
   }
        }
        pools.removeAllElements();

}


}

本文欢迎转载,转载时请说明出处.
3
3
分享到:
评论
6 楼 chbest 2008-07-02  
没做过数据源.
但是Connection 本来就是一个接口
然后jdbc得到的Connection和数据源得到的Connection 本来就是不一个实现.
jdbc得到的Connection执行close的时候有可能就是关闭连接,而数据源得到的Connection的close估计只是把Connection返回给数据源而已
5 楼 mfcai 2008-06-30  
这个确实是个难题.不过在实际的开发中,更多的会采用通过CodeReview的管理方式来解决.毕竟,写代码必须遵循一定的规范.team leader的职责之一就是审核代码.
4 楼 pandonix 2008-06-29  
还有一个问题,希望LZ不吝赐教:
假设已经按照LZ回答的方法解决了回收Connection的问题,那么如果在一个多人开发的项目中,组员的水平差别较大,或者说,总有那么些粗心的组员忘记close掉Connection,这些Connection是否一直都被占用?
LZ设计了TimeOut,但似乎是等待有效Connection的时间,而不是在使用的一个连接最长时间。
这样一来,在高并发的情况下,有不少Connection没有被close掉,就很可能造成Connection leak。
当然,可以通过CodeReview的管理方式来解决这个人为问题。而且proxool这样的连接池也没有解决这个问题。
有没有更优雅的解决方案呢?
以下是小弟的拙见:
返回的Connection可以是java.sql.Connection的一个子类,而由其实现finalize方法。在finalize中关闭Connection,并回收给缓冲池。
因为在Connection的使用中,通常都是局部变量,也就是说,该Connection引用会被GC所回收,而回收时会调用finalize方法。
以上方案是借鉴了FileInputStream的实现。
3 楼 mfcai 2008-06-29  
说的很正确.可以在调用Connection.close使用Proxy模式和java反射机制,调用releaseConnection()方法,将数据库链接返回给数据库连接池.
相关代码如下:
public class ConnectionHandler  implements InvocationHandler{
	private Connection conn;

	private ConnectionPool pool;

	public ConnectionHandler(ConnectionPool pool) {
		this.pool = pool;
	}

	/**
	 * 将动态代理绑定到指定Connection
	 * 
	 * @param conn
	 * @return
	 */
	public Connection bind(Connection conn) {
		this.conn = conn;
		Connection proxyConn = (Connection) Proxy.newProxyInstance(conn
				.getClass().getClassLoader(), conn.getClass().getInterfaces(),this);

		return proxyConn;
	}

	/*
	 * (非 Javadoc)
	 * 
	 * @see java.lang.reflect.InvocationHandler#invoke(java.lang.Object,
	 *      java.lang.reflect.Method, java.lang.Object[])
	 */
	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		// TODO自动生成方法存根
		Object obj = null;
		if ("close".equals(method.getName())) {
			this.pool.releaseConnection(this.conn);
		} else {
			obj = method.invoke(this.conn, args);
		}

		return obj;
	}
}
2 楼 pandonix 2008-06-29  
对于JDBC的操作,一般都习惯了如下的格式:
try
{
  con = getConnection;
  doDBWork;
}
catch(SQLException ex)
{
}
finally
{
  con.close();
}

LZ的getConnection返回的是java.sql.Connection对象,而当调用Connection.close是直接将connection释放掉了,而并非像c3p,proxool这样的连接池的做法一样,将Connection回收给缓冲池。这样,下次获取Connection还是需要重新创建,是否没有达到缓冲的目的呢?
换句话说,你没法让用户不调用Connection.close,而一旦调用了,就似乎失去了缓冲池的意思。
以上拙见,期待LZ的交流和讨论
1 楼 txxg 2008-06-29  
学习了!

相关推荐

Global site tag (gtag.js) - Google Analytics