鷹翔宇空

          學(xué)習(xí)和生活

          BlogJava 首頁(yè) 新隨筆 聯(lián)系 聚合 管理
            110 Posts :: 141 Stories :: 315 Comments :: 1 Trackbacks
          附:其實(shí),很多時(shí)候,原理大家也都很清楚,但是很多人就是懶的總結(jié)出來(lái),我就是一個(gè)例子,看到這篇文章后,轉(zhuǎn)載于此,一是學(xué)習(xí),二是鞭策。下面是原文。

          原文引自:http://www-128.ibm.com/developerworks/cn/java/l-connpoolproxy/



          作者通過(guò)使用JAVA中的動(dòng)態(tài)代理實(shí)現(xiàn)數(shù)據(jù)庫(kù)連接池,使使用者可以以普通的jdbc連接的使用習(xí)慣來(lái)使用連接池。

          數(shù)據(jù)庫(kù)連接池在編寫(xiě)應(yīng)用服務(wù)是經(jīng)常需要用到的模塊,太過(guò)頻繁的連接數(shù)據(jù)庫(kù)對(duì)服務(wù)性能來(lái)講是一個(gè)瓶頸,使用緩沖池技術(shù)可以來(lái)消除這個(gè)瓶頸。我們可以在互聯(lián)網(wǎng)上找到很多關(guān)于數(shù)據(jù)庫(kù)連接池的源程序,但是都發(fā)現(xiàn)這樣一個(gè)共同的問(wèn)題:這些連接池的實(shí)現(xiàn)方法都不同程度地增加了與使用者之間的耦合度。很多的連接池都要求用戶(hù)通過(guò)其規(guī)定的方法獲取數(shù)據(jù)庫(kù)的連接,這一點(diǎn)我們可以理解,畢竟目前所有的應(yīng)用服務(wù)器取數(shù)據(jù)庫(kù)連接的方式都是這種方式實(shí)現(xiàn)的。但是另外一個(gè)共同的問(wèn)題是,它們同時(shí)不允許使用者顯式的調(diào)用Connection.close()方法,而需要用其規(guī)定的一個(gè)方法來(lái)關(guān)閉連接。這種做法有兩個(gè)缺點(diǎn):

          第一:改變了用戶(hù)使用習(xí)慣,增加了用戶(hù)的使用難度。

          首先我們來(lái)看看一個(gè)正常的數(shù)據(jù)庫(kù)操作過(guò)程:

          int executeSQL(String sql) throws SQLException
          {
          	Connection conn = getConnection();	//通過(guò)某種方式獲取數(shù)據(jù)庫(kù)連接
          	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ù)庫(kù)連接后通常是直接調(diào)用連接的方法close來(lái)釋放數(shù)據(jù)庫(kù)資源,如果用我們前面提到的連接池的實(shí)現(xiàn)方法,那語(yǔ)句conn.close()將被某些特定的語(yǔ)句所替代。

          第二:使連接池?zé)o法對(duì)之中的所有連接進(jìn)行獨(dú)占控制。由于連接池不允許用戶(hù)直接調(diào)用連接的close方法,一旦使用者在使用的過(guò)程中由于習(xí)慣問(wèn)題直接關(guān)閉了數(shù)據(jù)庫(kù)連接,那么連接池將無(wú)法正常維護(hù)所有連接的狀態(tài),考慮連接池和應(yīng)用由不同開(kāi)發(fā)人員實(shí)現(xiàn)時(shí)這種問(wèn)題更容易出現(xiàn)。

          綜合上面提到的兩個(gè)問(wèn)題,我們來(lái)討論一下如何解決這兩個(gè)要命的問(wèn)題。

          首先我們先設(shè)身處地的考慮一下用戶(hù)是想怎么樣來(lái)使用這個(gè)數(shù)據(jù)庫(kù)連接池的。用戶(hù)可以通過(guò)特定的方法來(lái)獲取數(shù)據(jù)庫(kù)的連接,同時(shí)這個(gè)連接的類(lèi)型應(yīng)該是標(biāo)準(zhǔn)的java.sql.Connection。用戶(hù)在獲取到這個(gè)數(shù)據(jù)庫(kù)連接后可以對(duì)這個(gè)連接進(jìn)行任意的操作,包括關(guān)閉連接等。

          通過(guò)對(duì)用戶(hù)使用的描述,怎樣可以接管Connection.close方法就成了我們這篇文章的主題。

          為了接管數(shù)據(jù)庫(kù)連接的close方法,我們應(yīng)該有一種類(lèi)似于鉤子的機(jī)制。例如在Windows編程中我們可以利用Hook API來(lái)實(shí)現(xiàn)對(duì)某個(gè)Windows API的接管。在JAVA中同樣也有這樣一個(gè)機(jī)制。JAVA提供了一個(gè)Proxy類(lèi)和一個(gè)InvocationHandler,這兩個(gè)類(lèi)都在java.lang.reflect包中。我們先來(lái)看看SUN公司提供的文檔是怎么描述這兩個(gè)類(lèi)的。

          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的描述很多,這里就不羅列出來(lái)。通過(guò)文檔對(duì)接口InvocationHandler的描述我們可以看到當(dāng)調(diào)用一個(gè)Proxy實(shí)例的方法時(shí)會(huì)觸發(fā)Invocationhanlder的invoke方法。從JAVA的文檔中我們也同時(shí)了解到這種動(dòng)態(tài)代理機(jī)制只能接管接口的方法,而對(duì)一般的類(lèi)無(wú)效,考慮到j(luò)ava.sql.Connection本身也是一個(gè)接口由此就找到了解決如何接管close方法的出路。

          首先,我們先定義一個(gè)數(shù)據(jù)庫(kù)連接池參數(shù)的類(lèi),定義了數(shù)據(jù)庫(kù)的JDBC驅(qū)動(dòng)程序類(lèi)名,連接的URL以及用戶(hù)名口令等等一些信息,該類(lèi)是用于初始化連接池的參數(shù),具體定義如下:

          public class ConnectionParam implements Serializable
          {
          	private String driver;				//數(shù)據(jù)庫(kù)驅(qū)動(dòng)程序
          	private String url;					//數(shù)據(jù)連接的URL
          	private String user;					//數(shù)據(jù)庫(kù)用戶(hù)名
          	private String password;				//數(shù)據(jù)庫(kù)密碼
          	private int minConnection = 0;		//初始化連接數(shù)
          	private int maxConnection = 50;		//最大連接數(shù)
          	private long timeoutValue = 600000;//連接的最大空閑時(shí)間
          	private long waitTime = 30000;		//取連接的時(shí)候如果沒(méi)有可用連接最大的等待時(shí)間
          

          其次是連接池的工廠類(lèi)ConnectionFactory,通過(guò)該類(lèi)來(lái)將一個(gè)連接池對(duì)象與一個(gè)名稱(chēng)對(duì)應(yīng)起來(lái),使用者通過(guò)該名稱(chēng)就可以獲取指定的連接池對(duì)象,具體代碼如下:

          /**
           * 連接池類(lèi)廠,該類(lèi)常用來(lái)保存多個(gè)數(shù)據(jù)源名稱(chēng)合數(shù)據(jù)庫(kù)連接池對(duì)應(yīng)的哈希
           * @author liusoft
           */
          public class ConnectionFactory
          {
          	//該哈希表用來(lái)保存數(shù)據(jù)源名和連接池對(duì)象的關(guān)系表
          	static Hashtable connectionPools = null;
          	static{
          		connectionPools = new Hashtable(2,0.75F);
          	} 
          	/**
          	 * 從連接池工廠中獲取指定名稱(chēng)對(duì)應(yīng)的連接池對(duì)象
          	 * @param dataSource	連接池對(duì)象對(duì)應(yīng)的名稱(chēng)
          	 * @return DataSource	返回名稱(chēng)對(duì)應(yīng)的連接池對(duì)象
          	 * @throws NameNotFoundException	無(wú)法找到指定的連接池
          	 */
          	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ù)庫(kù)連接配置綁定在一起并初始化數(shù)據(jù)庫(kù)連接池
          	 * @param name		對(duì)應(yīng)連接池的名稱(chēng)
          	 * @param param	連接池的配置參數(shù),具體請(qǐng)見(jiàn)類(lèi)ConnectionParam
          	 * @return DataSource	如果綁定成功后返回連接池對(duì)象
          	 * @throws NameAlreadyBoundException	一定名字name已經(jīng)綁定則拋出該異常
          	 * @throws ClassNotFoundException		無(wú)法找到連接池的配置中的驅(qū)動(dòng)程序類(lèi)
          	 * @throws IllegalAccessException		連接池配置中的驅(qū)動(dòng)程序類(lèi)有誤
          	 * @throws InstantiationException		無(wú)法實(shí)例化驅(qū)動(dòng)程序類(lèi)
          	 * @throws SQLException				無(wú)法正常連接指定的數(shù)據(jù)庫(kù)
          	 */
          	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ù)庫(kù)連接池
          	 * @param name		對(duì)應(yīng)連接池的名稱(chēng)
          	 * @param param	連接池的配置參數(shù),具體請(qǐng)見(jiàn)類(lèi)ConnectionParam
          	 * @return DataSource	如果綁定成功后返回連接池對(duì)象
          	 * @throws NameAlreadyBoundException	一定名字name已經(jīng)綁定則拋出該異常
          	 * @throws ClassNotFoundException		無(wú)法找到連接池的配置中的驅(qū)動(dòng)程序類(lèi)
          	 * @throws IllegalAccessException		連接池配置中的驅(qū)動(dòng)程序類(lèi)有誤
          	 * @throws InstantiationException		無(wú)法實(shí)例化驅(qū)動(dòng)程序類(lèi)
          	 * @throws SQLException				無(wú)法正常連接指定的數(shù)據(jù)庫(kù)
          	 */
          	public static DataSource rebind(String name, ConnectionParam param)
          		throws NameAlreadyBoundException,ClassNotFoundException,
          				IllegalAccessException,InstantiationException,SQLException
          	{
          		try{
          			unbind(name);
          		}catch(Exception e){}
          		return bind(name, param);
          	}
          	/**
          	 * 刪除一個(gè)數(shù)據(jù)庫(kù)連接池對(duì)象
          	 * @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主要提供了用戶(hù)將將連接池綁定到一個(gè)具體的名稱(chēng)上以及取消綁定的操作。使用者只需要關(guān)心這兩個(gè)類(lèi)即可使用數(shù)據(jù)庫(kù)連接池的功能。下面我們給出一段如何使用連接池的代碼:

          	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.");
          	//以上代碼是用來(lái)登記一個(gè)連接池對(duì)象,該操作可以在程序初始化只做一次即可
          	//以下開(kāi)始就是使用者真正需要寫(xiě)的代碼
          	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);
          	}
          

          從使用者的示例代碼就可以看出,我們已經(jīng)解決了常規(guī)連接池產(chǎn)生的兩個(gè)問(wèn)題。但是我們最最關(guān)心的是如何解決接管close方法的辦法。接管工作主要在ConnectionFactory中的兩句代碼:

          source = new DataSourceImpl(param);
          source.initConnection();
          

          DataSourceImpl是一個(gè)實(shí)現(xiàn)了接口javax.sql.DataSource的類(lèi),該類(lèi)維護(hù)著一個(gè)連接池的對(duì)象。由于該類(lèi)是一個(gè)受保護(hù)的類(lèi),因此它暴露給使用者的方法只有接口DataSource中定義的方法,其他的所有方法對(duì)使用者來(lái)說(shuō)都是不可視的。我們先來(lái)關(guān)心用戶(hù)可訪問(wèn)的一個(gè)方法getConnection

          /**
           * @see javax.sql.DataSource#getConnection(String,String)
           */
          	public Connection getConnection(String user, String password) throws SQLException 
          	{
          		//首先從連接池中找出空閑的對(duì)象
          		Connection conn = getFreeConnection(0);
          		if(conn == null){
          			//判斷是否超過(guò)最大連接數(shù),如果超過(guò)最大連接數(shù)
          			//則等待一定時(shí)間查看是否有空閑連接,否則拋出異常告訴用戶(hù)無(wú)可用連接
          			if(getConnectionCount() >= connParam.getMaxConnection())
          				conn = getFreeConnection(connParam.getWaitTime());
          			else{//沒(méi)有超過(guò)連接數(shù),重新獲取一個(gè)數(shù)據(jù)庫(kù)的連接
          				connParam.setUser(user);
          				connParam.setPassword(password);
          				Connection conn2 = DriverManager.getConnection(connParam.getUrl(), 
          				user, password);
          				//代理將要返回的連接對(duì)象
          				_Connection _conn = new _Connection(conn2,true);
          				synchronized(conns){
          					conns.add(_conn);
          				}
          				conn = _conn.getConnection();
          			}
          		}
          		return conn;
          	}
          	/**
          	 * 從連接池中取一個(gè)空閑的連接
          	 * @param nTimeout	如果該參數(shù)值為0則沒(méi)有連接時(shí)只是返回一個(gè)null
          	 * 否則的話等待nTimeout毫秒看是否還有空閑連接,如果沒(méi)有拋出異常
          	 * @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("沒(méi)有可用的數(shù)據(jù)庫(kù)連接");
          		}
          		return conn;
          	}
          

          DataSourceImpl類(lèi)中實(shí)現(xiàn)getConnection方法的跟正常的數(shù)據(jù)庫(kù)連接池的邏輯是一致的,首先判斷是否有空閑的連接,如果沒(méi)有的話判斷連接數(shù)是否已經(jīng)超過(guò)最大連接數(shù)等等的一些邏輯。但是有一點(diǎn)不同的是通過(guò)DriverManager得到的數(shù)據(jù)庫(kù)連接并不是及時(shí)返回的,而是通過(guò)一個(gè)叫_Connection的類(lèi)中介一下,然后調(diào)用_Connection.getConnection返回的。如果我們沒(méi)有通過(guò)一個(gè)中介也就是JAVA中的Proxy來(lái)接管要返回的接口對(duì)象,那么我們就沒(méi)有辦法截住Connection.close方法。

          終于到了核心所在,我們先來(lái)看看_Connection是如何實(shí)現(xiàn)的,然后再介紹是客戶(hù)端調(diào)用Connection.close方法時(shí)走的是怎樣一個(gè)流程,為什么并沒(méi)有真正的關(guān)閉連接。

          /**
           * 數(shù)據(jù)連接的自封裝,屏蔽了close方法
           * @author Liudong
           */
          class _Connection implements InvocationHandler
          {
          	private final static String CLOSE_METHOD_NAME = "close";
          	private Connection conn = null;
          	//數(shù)據(jù)庫(kù)的忙狀態(tài)
          	private boolean inUse = false;
          	//用戶(hù)最后一次訪問(wèn)該連接方法的時(shí)間
          	private long lastAccessTime = System.currentTimeMillis();
          	
          	_Connection(Connection conn, boolean inUse){
          		this.conn = conn;
          		this.inUse = inUse;
          	}
          	/**
          	 * Returns the conn.
          	 * @return Connection
          	 */
          	public Connection getConnection() {
          		//返回?cái)?shù)據(jù)庫(kù)連接conn的接管類(lèi),以便截住close方法
          		Connection conn2 = (Connection)Proxy.newProxyInstance(
          			conn.getClass().getClassLoader(),
          			conn.getClass().getInterfaces(),this);
          		return conn2;
          	}
          	/**
          	 * 該方法真正的關(guān)閉了數(shù)據(jù)庫(kù)的連接
          	 * @throws SQLException
          	 */
          	void close() throws SQLException{
          		//由于類(lèi)屬性conn是沒(méi)有被接管的連接,因此一旦調(diào)用close方法后就直接關(guān)閉連接
          		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;
          		//判斷是否調(diào)用了close的方法,如果調(diào)用close方法則把連接置為無(wú)用狀態(tài)
          		if(CLOSE_METHOD_NAME.equals(m.getName()))
          			setInUse(false);		
          		else
          			obj = m.invoke(conn, args);	
          		//設(shè)置最后一次訪問(wèn)時(shí)間,以便及時(shí)清除超時(shí)的連接
          		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;
          	}
          }
          

          一旦使用者調(diào)用所得到連接的close方法,由于用戶(hù)的連接對(duì)象是經(jīng)過(guò)接管后的對(duì)象,因此JAVA虛擬機(jī)會(huì)首先調(diào)用_Connection.invoke方法,在該方法中首先判斷是否為close方法,如果不是則將代碼轉(zhuǎn)給真正的沒(méi)有被接管的連接對(duì)象conn。否則的話只是簡(jiǎn)單的將該連接的狀態(tài)設(shè)置為可用。到此您可能就明白了整個(gè)接管的過(guò)程,但是同時(shí)也有一個(gè)疑問(wèn):這樣的話是不是這些已建立的連接就始終沒(méi)有辦法真正關(guān)閉?答案是可以的。我們來(lái)看看ConnectionFactory.unbind方法,該方法首先找到名字對(duì)應(yīng)的連接池對(duì)象,然后關(guān)閉該連接池中的所有連接并刪除掉連接池。在DataSourceImpl類(lèi)中定義了一個(gè)close方法用來(lái)關(guān)閉所有的連接,詳細(xì)代碼如下:

          	/**
          	 * 關(guān)閉該連接池中的所有數(shù)據(jù)庫(kù)連接
          	 * @return int 返回被關(guān)閉連接的個(gè)數(shù)
          	 * @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;
          	}
          

          該方法一一調(diào)用連接池中每個(gè)對(duì)象的close方法,這個(gè)close方法對(duì)應(yīng)的是_Connection中對(duì)close的實(shí)現(xiàn),在_Connection定義中關(guān)閉數(shù)據(jù)庫(kù)連接的時(shí)候是直接調(diào)用沒(méi)有經(jīng)過(guò)接管的對(duì)象的關(guān)閉方法,因此該close方法真正的釋放了數(shù)據(jù)庫(kù)資源。

          以上文字只是描述了接口方法的接管,具體一個(gè)實(shí)用的連接池模塊還需要對(duì)空閑連接的監(jiān)控并及時(shí)釋放連接,詳細(xì)的代碼請(qǐng)參照附件。

          posted on 2006-05-10 23:23 TrampEagle 閱讀(363) 評(píng)論(0)  編輯  收藏 所屬分類(lèi): datebase
          主站蜘蛛池模板: 会泽县| 承德县| 进贤县| 汉沽区| 株洲县| 建平县| 来宾市| 昌吉市| 顺平县| 禄劝| 长海县| 扎赉特旗| 海兴县| 临漳县| 黔江区| 德惠市| 甘肃省| 庆云县| 黔西| 祁连县| 宁明县| 呼图壁县| 贵州省| 邮箱| 林口县| 都兰县| 武乡县| 佛冈县| 景谷| 镇赉县| 兖州市| 英吉沙县| 如皋市| 平果县| 百色市| 平乐县| 彭州市| 荆门市| 甘泉县| 西充县| 景德镇市|