1. gzyueqian
      13352868059
      首頁 > 新聞中心 > > 正文

      使用JAVA中的動(dòng)態(tài)代理實(shí)現(xiàn)數(shù)據(jù)庫連接池

      更新時(shí)間: 2008-04-28 14:59:00來源: 粵嵌教育瀏覽量:783

         【賽迪網(wǎng)技術(shù)社區(qū)整理】

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

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

        首先我們來看看一個(gè)正常的數(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ù)庫資源,如果用我們前面提到的連接池的實(shí)現(xiàn)方法,那語句conn.close()將被某些特定的語句所替代。

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

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

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

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

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

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

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

        public class ConnectionParam implements Serializable

        {

        private String driver; //數(shù)據(jù)庫驅(qū)動(dòng)程序

        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;//連接的空閑時(shí)間

        private long waitTime = 30000; //取連接的時(shí)候如果沒有可用連接的等待時(shí)間

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

        /**

        * 連接池類廠,該類常用來保存多個(gè)數(shù)據(jù)源名稱合數(shù)據(jù)庫連接池對(duì)應(yīng)的哈希

        * @author liusoft

        */

        public class ConnectionFactory

        {

        //該哈希表用來保存數(shù)據(jù)源名和連接池對(duì)象的關(guān)系表

        static Hashtable connectionPools = null;

        static{

        connectionPools = new Hashtable(2,0.75F);

        }

        /**

        * 從連接池工廠中獲取指定名稱對(duì)應(yīng)的連接池對(duì)象

        * @param dataSource 連接池對(duì)象對(duì)應(yīng)的名稱

        * @return DataSource 返回名稱對(duì)應(yīng)的連接池對(duì)象

        * @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 對(duì)應(yīng)連接池的名稱

        * @param param 連接池的配置參數(shù),具體請(qǐng)見類ConnectionParam

        * @return DataSource 如果綁定成功后返回連接池對(duì)象

        * @throws NameAlreadyBoundException 一定名字name已經(jīng)綁定則拋出該異常

        * @throws ClassNotFoundException 無法找到連接池的配置中的驅(qū)動(dòng)程序類

        * @throws IllegalAccessException 連接池配置中的驅(qū)動(dòng)程序類有誤

        * @throws InstantiationException 無法實(shí)例化驅(qū)動(dòng)程序類

        * @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 對(duì)應(yīng)連接池的名稱

        * @param param 連接池的配置參數(shù),具體請(qǐng)見類ConnectionParam

      * @return DataSource 如果綁定成功后返回連接池對(duì)象

        * @throws NameAlreadyBoundException 一定名字name已經(jīng)綁定則拋出該異常

        * @throws ClassNotFoundException 無法找到連接池的配置中的驅(qū)動(dòng)程序類

        * @throws IllegalAccessException 連接池配置中的驅(qū)動(dòng)程序類有誤

        * @throws InstantiationException 無法實(shí)例化驅(qū)動(dòng)程序類

        * @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);

        }

        /**

        * 刪除一個(gè)數(shù)據(jù)庫連接池對(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主要提供了用戶將將連接池綁定到一個(gè)具體的名稱上以及取消綁定的操作。使用者只需要關(guān)心這兩個(gè)類即可使用數(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.");

        //以上代碼是用來登記一個(gè)連接池對(duì)象,該操作可以在程序初始化只做一次即可

        //以下開始就是使用者真正需要寫的代碼

        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è)問題。但是我們關(guān)心的是如何解決接管close方法的辦法。接管工作主要在ConnectionFactory中的兩句代碼:

        source = new DataSourceImpl(param);

        source.initConnection();

        DataSourceImpl是一個(gè)實(shí)現(xiàn)了接口javax.sql.DataSource的類,該類維護(hù)著一個(gè)連接池的對(duì)象。由于該類是一個(gè)受保護(hù)的類,因此它暴露給使用者的方法只有接口DataSource中定義的方法,其他的所有方法對(duì)使用者來說都是不可視的。我們先來關(guā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){

        //判斷是否超過連接數(shù),如果超過連接數(shù)

        //則等待一定時(shí)間查看是否有空閑連接,否則拋出異常告訴用戶無可用連接

        if(getConnectionCount() >= connParam.getMaxConnection())

        conn = getFreeConnection(connParam.getWaitTime());

        else{//沒有超過連接數(shù),重新獲取一個(gè)數(shù)據(jù)庫的連接

        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則沒有連接時(shí)只是返回一個(gè)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("沒有可用的數(shù)據(jù)庫連接");

        }

        return conn;

        }

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

        終于到了核心所在,我們先來看看_Connection是如何實(shí)現(xiàn)的,然后再介紹是客戶端調(diào)用Connection.close方法時(shí)走的是怎樣一個(gè)流程,為什么并沒有真正的關(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ù)庫的忙狀態(tài)

        private boolean inUse = false;

        //用戶一次訪問該連接方法的時(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ù)庫連接conn的接管類,以便截住close方法

        Connection conn2 = (Connection)Proxy.newProxyInstance(

        conn.getClass().getClassLoader(),

        conn.getClass().getInterfaces(),this);

        return conn2;

        }

        /**

        * 該方法真正的關(guān)閉了數(shù)據(jù)庫的連接

        * @throws SQLException

        */

        void close() throws SQLException{

        //由于類屬性conn是沒有被接管的連接,因此一旦調(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方法則把連接置為無用狀態(tài)

        if(CLOSE_METHOD_NAME.equals(m.getName()))

        setInUse(false);

        else

        obj = m.invoke(conn, args);

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

        /**

        * 關(guān)閉該連接池中的所有數(shù)據(jù)庫連接

        * @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ù)庫連接的時(shí)候是直接調(diào)用沒有經(jīng)過接管的對(duì)象的關(guān)閉方法,因此該close方法真正的釋放了數(shù)據(jù)庫資源。

      免費(fèi)預(yù)約試聽課

      亚洲另类欧美综合久久图片区_亚洲中文字幕日产无码2020_欧美日本一区二区三区桃色视频_亚洲AⅤ天堂一区二区三区

      
      

      1. 日韩欧美无砖专区一中文字幕 | 一区二区性爱视频大全 | 综合网日日天干夜夜久久 | 午夜免费啪在线观看视频 | 亚洲精品乱码久久久久66 | 午夜a影院在线2017 |