使用JAVA中的動態(tài)代理完成數(shù)據(jù)庫連接池
發(fā)表時間:2024-05-22 來源:明輝站整理相關(guān)軟件相關(guān)文章人氣:
[摘要]數(shù)據(jù)庫連接池在編寫應用服務是經(jīng)常需要用到的模塊,太過頻繁的連接數(shù)據(jù)庫對服務性能來講是一個瓶頸,使用緩沖池技術(shù)可以來消除這個瓶頸。我們可以在互聯(lián)網(wǎng)上找到很多關(guān)于數(shù)據(jù)庫連接池的源程序,但是都發(fā)現(xiàn)這樣一個共同的問題:這些連接池的實現(xiàn)方法都不同程度地增加了與使用者之間的耦合度。很多的連接池都要求用戶通過其...
數(shù)據(jù)庫連接池在編寫應用服務是經(jīng)常需要用到的模塊,太過頻繁的連接數(shù)據(jù)庫對服務性能來講是一個瓶頸,使用緩沖池技術(shù)可以來消除這個瓶頸。我們可以在互聯(lián)網(wǎng)上找到很多關(guān)于數(shù)據(jù)庫連接池的源程序,但是都發(fā)現(xiàn)這樣一個共同的問題:這些連接池的實現(xiàn)方法都不同程度地增加了與使用者之間的耦合度。很多的連接池都要求用戶通過其規(guī)定的方法獲取數(shù)據(jù)庫的連接,這一點我們可以理解,畢竟目前所有的應用服務器取數(shù)據(jù)庫連接的方式都是這種方式實現(xiàn)的。但是另外一個共同的問題是,它們同時不允許使用者顯式的調(diào)用Connection.close()方法,而需要用其規(guī)定的一個方法來關(guān)閉連接。這種做法有兩個缺點:
第一:改變了用戶使用習慣,增加了用戶的使用難度。
首先我們來看看一個正常的數(shù)據(jù)庫操作過程:
int executeSQL(String sql) throws SQLException
{
Connection conn = getConnection(); //通過某種方式獲取數(shù)據(jù)庫連接
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;
}
使用者在用完數(shù)據(jù)庫連接后通常是直接調(diào)用連接的方法close來釋放數(shù)據(jù)庫資源,如果用我們前面提到的連接池的實現(xiàn)方法,那語句conn.close()將被某些特定的語句所替代。
第二:使連接池無法對之中的所有連接進行獨占控制。由于連接池不允許用戶直接調(diào)用連接的close方法,一旦使用者在使用的過程中由于習慣問題直接關(guān)閉了數(shù)據(jù)庫連接,那么連接池將無法正常維護所有連接的狀態(tài),考慮連接池和應用由不同開發(fā)人員實現(xiàn)時這種問題更容易出現(xiàn)。
綜合上面提到的兩個問題,我們來討論一下如何解決這兩個要命的問題。
首先我們先設(shè)身處地的考慮一下用戶是想怎么樣來使用這個數(shù)據(jù)庫連接池的。用戶可以通過特定的方法來獲取數(shù)據(jù)庫的連接,同時這個連接的類型應該是標準的java.sql.Connection。用戶在獲取到這個數(shù)據(jù)庫連接后可以對這個連接進行任意的操作,包括關(guān)閉連接等。
通過對用戶使用的描述,怎樣可以接管Connection.close方法就成了我們這篇文章的主題。
為了接管數(shù)據(jù)庫連接的close方法,我們應該有一種類似于鉤子的機制。例如在Windows編程中我們可以利用Hook API來實現(xiàn)對某個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文檔中關(guān)于Proxy的描述很多,這里就不羅列出來。通過文檔對接口InvocationHandler的描述我們可以看到當調(diào)用一個Proxy實例的方法時會觸發(fā)Invocationhanlder的invoke方法。從JAVA的文檔中我們也同時了解到這種動態(tài)代理機制只能接管接口的方法,而對一般的類無效,考慮到j(luò)ava.sql.Connection本身也是一個接口由此就找到了解決如何接管close方法的出路。
首先,我們先定義一個數(shù)據(jù)庫連接池參數(shù)的類,定義了數(shù)據(jù)庫的JDBC驅(qū)動程序類名,連接的URL以及用戶名口令等等一些信息,該類是用于初始化連接池的參數(shù),具體定義如下:
public class ConnectionParam implements Serializable
{
private String driver; //數(shù)據(jù)庫驅(qū)動程序
private String url; //數(shù)據(jù)連接的URL
private String user; //數(shù)據(jù)庫用戶名
private String password; //數(shù)據(jù)庫密碼
private int minConnection = 0; //初始化連接數(shù)
private int maxConnection = 50; //最大連接數(shù)
private long timeoutValue = 600000;//連接的最大空閑時間
private long waitTime = 30000; //取連接的時候如果沒有可用連接最大的等待時間
其次是連接池的工廠類ConnectionFactory,通過該類來將一個連接池對象與一個名稱對應起來,使用者通過該名稱就可以獲取指定的連接池對象,具體代碼如下:
/**
* 連接池類廠,該類常用來保存多個數(shù)據(jù)源名稱合數(shù)據(jù)庫連接池對應的哈希
* @author liusoft
*/
public class ConnectionFactory
{
//該哈希表用來保存數(shù)據(jù)源名和連接池對象的關(guān)系表
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;
}
/**
* 將指定的名字和數(shù)據(jù)庫連接配置綁定在一起并初始化數(shù)據(jù)庫連接池
* @param name 對應連接池的名稱
* @param param 連接池的配置參數(shù),具體請見類ConnectionParam
* @return DataSource 如果綁定成功后返回連接池對象
* @throws NameAlreadyBoundException 一定名字name已經(jīng)綁定則拋出該異常
* @throws ClassNotFoundException 無法找到連接池的配置中的驅(qū)動程序類
* @throws IllegalAccessException 連接池配置中的驅(qū)動程序類有誤
* @throws InstantiationException 無法實例化驅(qū)動程序類
* @throws SQLException 無法正常連接指定的數(shù)據(jù)庫
*/
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;
}
/**
* 重新綁定數(shù)據(jù)庫連接池
* @param name 對應連接池的名稱
* @param param 連接池的配置參數(shù),具體請見類ConnectionParam
* @return DataSource 如果綁定成功后返回連接池對象
* @throws NameAlreadyBoundException 一定名字name已經(jīng)綁定則拋出該異常
* @throws ClassNotFoundException 無法找到連接池的配置中的驅(qū)動程序類
* @throws IllegalAccessException 連接池配置中的驅(qū)動程序類有誤
* @throws InstantiationException 無法實例化驅(qū)動程序類
* @throws SQLException 無法正常連接指定的數(shù)據(jù)庫
*/
public static DataSource rebind(String name, ConnectionParam param)
throws NameAlreadyBoundException,ClassNotFoundException,
IllegalAccessException,InstantiationException,SQLException
{
try{
unbind(name);
}catch(Exception e){}
return bind(name, param);
}
/**
* 刪除一個數(shù)據(jù)庫連接池對象
* @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);
}
}
ConnectionFactory主要提供了用戶將將連接池綁定到一個具體的名稱上以及取消綁定的操作。使用者只需要關(guān)心這兩個類即可使用數(shù)據(jù)庫連接池的功能。下面我們給出一段如何使用連接池的代碼:
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);
}