<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    The important thing in life is to have a great aim , and the determination

    常用鏈接

    統計

    IT技術鏈接

    保險相關

    友情鏈接

    基金知識

    生活相關

    最新評論

    使用JAVA中的動態代理實現數據庫連接池(轉自developerWorks )

     

    通過使用JAVA中的動態代理實現數據庫連接池,使使用者可以以普通的jdbc連接的使用習慣來使用連接池。

    數據庫連接池在編寫應用服務是經常需要用到的模塊,太過頻繁的連接數據庫對服務性能來講是一個瓶頸,使用緩沖池技術可以來消除這個瓶頸。我們可以在互聯網上找到很多關于數據庫連接池的源程序,但是都發現這樣一個共同的問題:這些連接池的實現方法都不同程度地增加了與使用者之間的耦合度。很多的連接池都要求用戶通過其規定的方法獲取數據庫的連接,這一點我們可以理解,畢竟目前所有的應用服務器取數據庫連接的方式都是這種方式實現的。但是另外一個共同的問題是,它們同時不允許使用者顯式的調用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.

    SUNAPI文檔中關于Proxy的描述很多,這里就不羅列出來。通過文檔對接口InvocationHandler的描述我們可以看到當調用一個Proxy實例的方法時會觸發Invocationhanlderinvoke方法。從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);

                   }

                  

    }

    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();

    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;

                   }

    }

    一旦使用者調用所得到連接的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方法真正的釋放了數據庫資源。

    posted on 2010-07-14 17:08 鴻雁 閱讀(148) 評論(0)  編輯  收藏


    只有注冊用戶登錄后才能發表評論。


    網站導航:
     
    主站蜘蛛池模板: 国产成人99久久亚洲综合精品| 亚洲人成色在线观看| 国产一级一片免费播放i| 暖暖日本免费中文字幕| 成人特级毛片69免费观看| 日本亚洲免费无线码 | 亚洲国产高清人在线| 亚洲成A人片在线观看无码3D| 在线永久免费的视频草莓| a毛片在线免费观看| 鲁死你资源站亚洲av| 77777午夜亚洲| 亚洲精品人成电影网| 亚洲AV无码专区在线播放中文 | 国产成人精品日本亚洲专区6| 亚洲不卡av不卡一区二区| 精品亚洲成α人无码成α在线观看| 日本特黄a级高清免费大片| 手机在线看永久av片免费| 99re这里有免费视频精品| 十八禁视频在线观看免费无码无遮挡骂过 | 免费看黄福利app导航看一下黄色录像| 亚洲成年网站在线观看| 亚洲人和日本人jizz| 亚洲酒色1314狠狠做| 久久亚洲日韩精品一区二区三区| 亚洲av无码av制服另类专区| 久久九九亚洲精品| 国产精品亚洲精品日韩已满| 亚洲女同成av人片在线观看| 亚洲中文字幕久久精品无码APP | 亚洲AV一二三区成人影片| 亚洲国产高清美女在线观看| 国产精品成人免费福利| 国产成人AV免费观看| 丁香花在线观看免费观看图片 | 免费在线观看的网站| 成人影片麻豆国产影片免费观看| 免费电影在线观看网站| 免费观看一级毛片| 国产成人免费a在线资源|