使用 JAVA 中的动态代理实现数据库连接池
数据库连接池在编写应用服务是经常需要用到的模块,太过频繁的连接数据库对服务性能来讲是一个瓶颈,使用缓冲池技术可以来消除这个瓶颈。我们可以在 互联网上找到很多关于数据库连接池的源程序,但是都发现这样一个共同的问题:这些连接池的实现方法都不同程度地增加了与使用者之间的耦合度。很多的连接池 都要求用户通过其规定的方法获取数据库的连接,这一点我们可以理解,毕竟目前所有的应用服务器取数据库连接的方式都是这种方式实现的。但是另外一个共同的 问题是,它们同时不允许使用者显式的调用 Connection.close() 方法,而需要用其规定的一个方法来关闭连接。这种做法有两个缺点:
第一:改变了用户使用习惯,增加了用户的使用难度。
首先我们来看看一个正常的数据库操作过程:
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; } /** * 连接池类厂,该类常用来保存多个数据源名称合数据库连接池对应的哈希 * @author liusoft */ public class ConnectionFactory { // 该哈希表用来保存数据源名和连接池对象的关系表 static Hashtable connectionPools = null; static{ connectionPools = new Hashtable(2,0.75F); } /** * 从连接池工厂中获取指定名称对应的连接池对象 * @param dataSource 连接池对象对应的名称 * @return DataSource 返回名称对应的连接池对象 * @throws NameNotFoundException 无法找到指定的连接池 */ 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; } /** * 将指定的名字和数据库连接配置绑定在一起并初始化数据库连接池 * @param name 对应连接池的名称 * @param param 连接池的配置参数,具体请见类 ConnectionParam * @return DataSource 如果绑定成功后返回连接池对象 * @throws NameAlreadyBoundException 一定名字 name 已经绑定则抛出该异常 * @throws ClassNotFoundException 无法找到连接池的配置中的驱动程序类 * @throws IllegalAccessException 连接池配置中的驱动程序类有误 * @throws InstantiationException 无法实例化驱动程序类 * @throws SQLException 无法正常连接指定的数据库 */ 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; } /** * 重新绑定数据库连接池 * @param name 对应连接池的名称 * @param param 连接池的配置参数,具体请见类 ConnectionParam * @return DataSource 如果绑定成功后返回连接池对象 * @throws NameAlreadyBoundException 一定名字 name 已经绑定则抛出该异常 * @throws ClassNotFoundException 无法找到连接池的配置中的驱动程序类 * @throws IllegalAccessException 连接池配置中的驱动程序类有误 * @throws InstantiationException 无法实例化驱动程序类 * @throws SQLException 无法正常连接指定的数据库 */ public static DataSource rebind(String name, ConnectionParam param) throws NameAlreadyBoundException,ClassNotFoundException, IllegalAccessException,InstantiationException,SQLException { try{ unbind(name); }catch(Exception e){} return bind(name, param); } /** * 删除一个数据库连接池对象 * @param name * @throws NameNotFoundException */ 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); } } 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); } source = new DataSourceImpl(param); source.initConnection();
/** * @see javax.sql.DataSource#getConnection(String,String) */ 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; } /** * 从连接池中取一个空闲的连接 * @param nTimeout 如果该参数值为 0 则没有连接时只是返回一个 null * 否则的话等待 nTimeout 毫秒看是否还有空闲连接,如果没有抛出异常 * @return Connection * @throws SQLException */ 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){ // 等待 nTimeout 毫秒以便看是否有空闲连接 try{ Thread.sleep(nTimeout); }catch(Exception e){} conn = getFreeConnection(0); if(conn == null) throw new SQLException("没有可用的数据库连接"); } return conn; }/** * 数据连接的自封装,屏蔽了 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; } /** * Returns the conn. * @return Connection */ public Connection getConnection() { // 返回数据库连接 conn 的接管类,以便截住 close 方法 Connection conn2 = (Connection)Proxy.newProxyInstance( conn.getClass().getClassLoader(), conn.getClass().getInterfaces(),this); return conn2; } /** * 该方法真正的关闭了数据库的连接 * @throws SQLException */ void close() throws SQLException{ // 由于类属性 conn 是没有被接管的连接,因此一旦调用 close 方法后就直接关闭连接 conn.close(); } /** * Returns the inUse. * @return boolean */ public boolean isInUse() { return inUse; } /** * @see java.lang.reflect.InvocationHandler#invoke(java.lang.Object, * java.lang.reflect.Method, java.lang.Object) */ public Object invoke(Object proxy, Method m, Object[] args) throws Throwable { Object obj = null; // 判断是否调用了 close 的方法,如果调用 close 方法则把连接置为无用状态 if(CLOSE_METHOD_NAME.equals(m.getName())) setInUse(false); else obj = m.invoke(conn, args); // 设置最后一次访问时间,以便及时清除超时的连接 lastAccessTime = System.currentTimeMillis(); return obj; } /** * Returns the lastAccessTime. * @return long */ public long getLastAccessTime() { return lastAccessTime; } /** * Sets the inUse. * @param inUse The inUse to set */ public void setInUse(boolean inUse) { this.inUse = inUse; } } /** * 关闭该连接池中的所有数据库连接 * @return int 返回被关闭连接的个数 * @throws SQLException */ 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; }