利用JAVA中的动态署理实现数据库毗连池
副标题#e#
数据库毗连池在编写应用处事是常常需要用到的模块,过分频繁的毗连数据 库对处事机能来讲是一个瓶颈,利用缓冲池技能可以来消除这个瓶颈。我们可以 在互联网上找到许多关于数据库毗连池的源措施,可是都发明这样一个配合的问 题:这些毗连池的实现要领都差异水平地增加了与利用者之间的耦合度。许多的 毗连池都要求用户通过其划定的要领获取数据库的毗连,这一点我们可以领略, 究竟今朝所有的应用处事器取数据库毗连的方法都是这种方法实现的。可是别的 一个配合的问题是,它们同时不答允利用者显式的挪用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;
}
利用者在用完数据库毗连后凡是是直接挪用毗连的要领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.
#p#副标题#e#
SUN的API文档中关于Proxy的描写许多,这里就不摆列出来。通过文档对接口 InvocationHandler的描写我们可以看到当挪用一个Proxy实例的要领时会触发 Invocationhanlder的invoke要领。从JAVA的文档中我们也同时相识到这种动态 署理机制只能经受接口的要领,而对一般的类无效,思量到 java.sql.Connection自己也是一个接口由此就找到了办理如何经受close要领的 出路。
首先,我们先界说一个数据库毗连池参数的类,界说了数据库的JDBC驱动程 序类名,毗连的URL以及用户名口令等等一些信息,该类是用于初始化毗连池的 参数,详细界说如下:
public class ConnectionParam implements Serializable
{
private String driver; //数据库驱动措施
private String url; //数据毗连的URL
private String user; //数据库用户名
private String password; //数据库暗码
private int minConnection = 0; //初始化毗连数
private int maxConnection = 50; //最大毗连数
private long timeoutValue = 600000;//毗连的最大空闲时间
private long waitTime = 30000; //取毗连的时候假如没有可用毗连最大的 期待时间
其次是毗连池的工场类ConnectionFactory,通过该类来将一个毗连池工具与 一个名称对应起来,利用者通过该名称就可以获取指定的毗连池工具,详细代码 如下:
/**
* 毗连池类厂,该类常用来生存多个数据源名称合数据库毗连池对应的哈希
* @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);
}
}
#p#分页标题#e#
ConnectionFactory主要提供了用户将将毗连池绑定到一个详细的名称上以及 打消绑定的操纵。利用者只需要体贴这两个类即可利用数据库毗连池的成果。下 面我们给出一段如何利用毗连池的代码:
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中的两句代码:
source = new DataSourceImpl(param);
source.initConnection();
#p#分页标题#e#
DataSourceImpl是一个实现了接口javax.sql.DataSource的类,该类维护着 一个毗连池的工具。由于该类是一个受掩护的类,因此它袒露给利用者的要领只 有接口DataSource中界说的要领,其他的所有要领对利用者来说都是不行视的。 我们先来体贴用户可会见的一个要领getConnection
/**
* @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;
}
DataSourceImpl类中实现getConnection要领的跟正常的数据库毗连池的逻辑 是一致的,首先判定是否有空闲的毗连,假如没有的话判定毗连数是否已经高出 最大毗连数等等的一些逻辑。可是有一点差异的是通过DriverManager获得的数 据库毗连并不是实时返回的,而是通过一个叫_Connection的类中介一下,然后 挪用_Connection.getConnection返回的。假如我们没有通过一其中介也就是 JAVA中的Proxy来经受要返回的接口工具,那么我们就没有步伐截住 Connection.close要领。
终于到了焦点地址,我们先来看看_Connection是如何实现的,然后再先容是 客户端挪用Connection.close要领时走的是奈何一个流程,为什么并没有真正的 封锁毗连。
/**
* 数据毗连的自封装,屏蔽了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;
}
}
#p#分页标题#e#
一旦利用者挪用所获得毗连的close要领,由于用户的毗连工具是颠末经受后 的工具,因此JAVA虚拟时机首先挪用_Connection.invoke要领,在该要领中首先 判定是否为close要领,假如不是则将代码转给真正的没有被经受的毗连工具 conn。不然的话只是简朴的将该毗连的状态配置为可用。到此您大概就大白了整 个经受的进程,可是同时也有一个疑问:这样的话是不是这些已成立的毗连就始 终没有步伐真正封锁?谜底是可以的。我们来看看ConnectionFactory.unbind方 法,该要领首先找到名字对应的毗连池工具,然后封锁该毗连池中的所有毗连并 删除去毗连池。在DataSourceImpl类中界说了一个close要领用来封锁所有的连 接,具体代码如下:
/**
* 封锁该毗连池中的所有数据库毗连
* @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;
}
该要领一一挪用毗连池中每个工具的close要领,这个close要领对应的是 _Connection中对close的实现,在_Connection界说中封锁数据库毗连的时候是 直接挪用没有颠末经受的工具的封锁要领,因此该close要领真正的释放了数据 库资源。
以上文字只是描写了接口要领的经受,详细一个实用的毗连池模块还需要对 空闲毗连的监控并实时释放毗连,具体的代码请参照附件。