摘要:當一個可以被回收的時候,將會使用風格的事件去通知連接池管理器應用服務器。為了發生連接事件時能被通知到,連接池管理器必須實現接口,然后會將其注冊為連接事件的一個監聽者。
在基本的 DataSource 實現中,客戶端的 Connection 對象與物理數據庫連接有著1:1的關系。當 Connection 被關閉以后,物理連接也會被關閉。因此,連接的頻繁打開、初始化以及關閉,會在一個客戶端會話中上演多次,帶來了過重的性能消耗。
而連接池就能解決這個問題,連接池維護了一系列物理數據庫連接的緩存,可以被多個客戶端會話重復使用。連接池能夠極大地提高性能和可擴展性,特別是在一個三層架構的環境中,大量的客戶端可以共享一個數量比較小的物理數據庫連接池。在圖11-1中,JDBC 驅動提供了一個 ConnectionPoolDataSource 的實現,應用服務器可以用它來創建和管理連接池。
連接池的管理策略跟具體的實現有關,也跟具體的應用服務器有關。應用服務器對客戶端提供了一個 DataSource 接口的具體實現,使得連接池化對于客戶端來說是透明的。最終,客戶端使用 DataSource API 就能和之前使用 JNDI 一樣,獲得了更好的性能和可擴展性。
下文將會介紹 ConnectionPoolDataSource 接口、PooledConnection 接口以及 ConnectionEvent 類,這三個組成部分是一個相互合作的關系,下文將以一個經典線程池的實現的角度,逐步描述這幾部分。這一章也會介紹基本的 DataSource 對象和池化的 DataSource 對象之間的區別,此外,還會討論一個池化的連接如何能夠維護一堆可重用的 PreparedStatement 對象。
盡管本章中的所有討論都是假設在三層架構環境下的,但連接的池化在兩層架構的環境下也同樣有用。
在兩層架構的環境中,JDBC 驅動既實現了 DataSource 接口,也實現 ConnectionPoolDataSource 接口,這種實現方式允許客戶端打開或者關閉多個連接。
一般來說, 一個 JDBC 驅動會去實現 ConnectionPoolDataSource 接口,應用服務器可以使用這個接口來獲得 PooledConnection 對象,以下代碼展示了 getPooledConnection 方法的兩種版本
public interface ConnectionPoolDataSource { PooledConnection getPooledConnection() throws SQLException; PooledConnection getPooledConnection(String user, String password) throws SQLException; }
一個 PooledConnection 對象代表一條與數據源之間的物理連接。JDBC 驅動對于 PooledConnection 的實現,則會封裝所有與維護這條連接相關的細節。
應用服務器則會在它的 DataSource 接口的實現中,緩存和重用這些 PooledConnection。當客戶端調用 DataSource.getConnection 方法時,應用服務器將會使用物理 PooledConnection 去獲取一個邏輯 Connection 對象。以下代碼是 PooledConnection 接口的一些方法定義:
public interface PooledConnection { Connection getConnection() throws SQLException; void close() throws SQLException; void addConnectionEventListener( ConnectionEventListener listener); void addStatementEventListener( StatementEventListener listener); void removeConnectionEventListener( ConnectionEventListener listener); void removeStatementEventListener( StatementEventListener listener); }
當客戶端使用完連接以后,它使用 Connection.close 方法來關閉這條邏輯連接,這個動作只是關閉了邏輯連接,但并不會關閉物理連接。物理連接會被歸還到池子里,以待重用。
在這里,連接的池化對于客戶端來說完全是透明的,客戶端能像使用非池化連接那樣去使用池化連接。
需要注意的是,當對池化的連接調用 Connection.close() 方法時,之前通過 Connection.setClientInfo 設置的屬性將會被清除掉。11.2 連接事件
回憶先前說過的,當 Connection.close 方法被調用后,底層的物理連接 PooledConnection 就可以再次被重用。當一個 PooledConnection 可以被回收的時候,將會使用 JavaBean 風格的事件去通知連接池管理器(應用服務器)。
為了發生連接事件時能被通知到,連接池管理器必須實現 ConnectionEventListener 接口,然后 PooledConnection 會將其注冊為連接事件的一個監聽者。ConnectionEventListener 接口定義了兩個方法,也體現出了可能發生的兩種不同的事件:
connectionClosed 事件 --- 當邏輯連接 Connection.close 被調用時產生此事件
connectionErrorOccurred --- 當出現一些致命的錯誤,比如說數據庫宕機導致連接丟失的時候,會觸發這個事件
連接池管理器通過調用 PooledConnection.addConnectionEventListener 方法來將自己注冊為一個 PooledConnection 的監聽者。一般情況下,注冊的動作都發生在將連接歸還到池子里之前。
JDBC 驅動負責在對應的事件發生的時候,調用回調方法,這兩個方法都需要一個 ConnectionEvent 對象作為參數,通過這個對象可以判斷到底是哪個 PooledConnection 被關閉了或者發生了錯誤。
當客戶端關閉了邏輯連接的時候,JDBC 驅動會通過調用監聽者所實現的 connectionClosed 方法來通知監聽者,此時,監聽者(連接池管理器)可以將該連接歸還到池子里以便重用。當致命性錯誤發生時,JDBC 驅動首先會調用監聽者實現的 connectionErrorOccurred 方法,然后再拋出一個 SQLException 異常。這個時候,監聽者就可以通過 PooledConnection.close 方法來將物理連接關閉。
以下步驟列出了客戶端使用連接池池化時,實際上發生的事情:
客戶端調用 DataSource.getConnection 方法
應用服務器在它自己支持連接池的 DataSource 實現中,查找是否有可用的 PooledConnection 對象
如果沒有可用的 PooledConnection 對象,應用服務器調用 ConnectionPoolDataSource.getPooledConnection 來創建一條物理連接,JDBC 驅動的具體實現會負責連接創建的具體細節,并把它交給應用服務器管理。
無論是新建的 PooledConnection 還是已經創建好的處于可用狀態的,應用服務器會對這條連接進行一些標識,標記它處于正在使用的狀態。
應用服務器調用 PooledConnection.getConnection 方法來獲得一個邏輯上的 Connection 對象,這個對象底層實際上關聯了一個物理的 PooledConnection 對象,客戶端調用 DataSource.getConnection 方法,返回值拿到的是邏輯上的 Connection 對象。
應用服務器通過調用 PooledConnection.addConnectionEventListener 方法將它自己注冊為一個 ConnectionEventListener,當 PooledConnection 處于可用狀態時,應用服務器就會得到相應的事件通知。
由 DataSource.getConnection 方法返回的邏輯 Connection 對象,依然是使用 Connection API,直到 Connection.close 被調用之前,底層的 PooledConnection 都處于使用狀態,不可被重用。
即使在沒有應用服務器的兩層架構環境中,連接依然可以做到池化。這種情況下,JDBC 驅動需要實現 DataSource 接口和 ConnectionPoolDataSource 接口。
11.4 DataSource 實現與連接池化拋開對性能和擴展性的提升不說,客戶端使用 DataSource 接口的時候,不需要去關心它底層的實現是否池化,客戶端面向的是一套統一的,無差別的使用方式。
常規的 DataSource 實現,即不實現連接池化功能的實現,一般由 JDBC 驅動實現,通常有以下兩個觀點被認為是正確的:
DataSource.getConnection 方法創建一個新的 Connection 對象來代表一條真正的物理連接,并且封裝了所有維護和管理這條物理連接的細節。
Connection.close 方法關閉底層的物理連接并釋放相關的資源
在一個實現了池化的 DataSource 實現中,情況則有些不一樣,以下幾個觀點被認為是正確的:
在 DataSource 的實現中,包含了一個提供了連接池化功能的模塊,這個模塊要怎么實現沒有一個統一的標準,因人而異。這個模塊會緩存一系列 PooledConnection 對象。DataSource 的實現類,通常處于驅動實現的 ConnectionPoolDataSource 和 PooledConnection 接口的上層。
DataSource.getConnection 方法會調用 PooledConnection 方法去獲得對底層物理連接的一個句柄,如果已有的連接池里沒有現成可用的連接,那么這個時候就需要新建物理連接,只有在這種情況下,新建物理連接對性能的消耗才體現出來。當需要創建新的物理連接的時候,ConnectionPoolDataSource 的 getPooledConnection 會被調用,對于物理連接的管理細節,則委托給了 PooledConnection 對象。
Connection.close 方法被調用時,只是關閉邏輯上的連接句柄,并不會關閉實際上的物理連接。連接池管理者此時會收到一個事件通知,被告知一個 PooledConnection 處于可重用狀態了。如果此時客戶端仍然企圖使用這個邏輯上的連接句柄,那么只會得到一個 SQLException 異常。
一個物理 PooledConnection 在它的整個生命周期中,可能會產生許多的邏輯 Connection 對象,但只有最近一次產生的 Connection 對象才是有效的,當 PooledConnection.getConnection 方法被調用時,先前已經存在的 Connection 對象,將會被自動關閉。這種情況下,關閉不會產生相應的事件去通知監聽者。
這給了應用服務器一種從客戶端強行拿走連接的方式,這種情形可能很少見,但是當應用服務器需要進行強制關閉時,這個特性可能會很有用
連接池的管理者通過調用 PooledConnection.close 方法來關閉物理連接,一般發生以下情況時,才會這么做:當應用服務器正常退出時,當需要重新初始化連接的緩存時,或者是該連接上發生一些不可恢復的致命性錯誤時。
11.5 部署進行連接池化的部署,需要提供一個客戶端代碼可以接觸到的 DataSource 對象,并且還需要把一個 ConnectionPoolDataSource 對象注冊到 JNDI 中。
第一步,部署 ConnectionPoolDataSource,如下代碼所示:
// ConnectionPoolDS implements the ConnectionPoolDataSource // interface. Create an instance and set properties. com.acme.jdbc.ConnectionPoolDS cpds = new com.acme.jdbc.ConnectionPoolDS(); cpds.setServerName(“bookserver”); cpds.setDatabaseName(“booklist”); cpds.setPortNumber(9040); cpds.setDescription(“Connection pooling for bookserver”); // Register the ConnectionPoolDS with JNDI, using the logical name // “jdbc/pool/bookserver_pool” Context ctx = new InitialContext(); ctx.bind(“jdbc/pool/bookserver_pool”, cpds);
上述步驟做好以后,ConnectionPoolDataSource 對象就可以被對客戶端代碼可見的 DataSource 使用了,DataSource 的部署需要依賴于先前部署的 ConnectionPoolDataSource,如下代碼所示:
// PooledDataSource implements the DataSource interface. // Create an instance and set properties. com.acme.appserver.PooledDataSource ds = new com.acme.appserver.PooledDataSource(); ds.setDescription(“Datasource with connection pooling”); // Reference the previously registered ConnectionPoolDataSource ds.setDataSourceName(“jdbc/pool/bookserver_pool”); // Register the DataSource implementation with JNDI, using the logical // name “jdbc/bookserver”. Context ctx = new InitialContext(); ctx.bind(“jdbc/bookserver”, ds);
到此,客戶端代碼就可以使用這個 DataSource 了。
11.6 池化連接的 Statement 重用JDBC 規范對于 statement 的池化也提供了一些支持。statement 池化這個特性,能讓應用層像 connection 重用一樣,對 PreparedStatement 進行重用,這個特性需要以連接池化為基礎。
下圖展示了 PooledConnection 與 PreparedStament 之間的關系。邏輯 Connection 可以透明地使用多個 PreparedStatement 對象。
上圖中,連接池和 statement 池由應用服務器來實現。不過,這些功能其實也可以由驅動來實現,或者是數據源來實現。這里我們對于 statement 池化的討論,其實是適用于以上提到的所有實現方式的。
11.6.1 使用池化 Statement對于 statement 的重用,必須對應用透明。也就是說,從應用開發的角度,對一個 statement 的使用,不需要關心它是否是池化的實現。statement 在底層會一直保持處于打開狀態,應用層的代碼也不需要改變。如果應用層關閉了這個 statement,它依然需要調用 Connection.prepareStatement 方法來繼續使用它。statement 的池化對于應用層來說,使用方式上是透明的,應用層唯一能感知到不同的,是它帶來的明顯的性能提升。
應用層需要通過調用 DatabaseMetadata 的 supportStatementPooling 方法,來判斷一個數據源是否支持 statement 重用。
在很多情況下,對于 statement 的重用,是一種非常有意義的優化,尤其是負責的 prepared statement。不過,需要注意的是,大量的 statement 處于打開狀態,有可能會對資源帶來影響。
一旦應用層關閉了一個 statement,無論它是否是池化的,它都不能再繼續被使用了,否則會導致異常拋出。
以下幾個方法會關閉一個池化的 statement:
Statement.close --- 由應用層調用。如果一個 statement 是池化的,調用這個方法會關閉邏輯上的 statement,但不會關閉底層的已經池化的物理 statement。
Connection.close --- 由應用層調用。
非池化連接 --- 關閉底層的物理連接和由這個連接創建的所有 statement。這樣做是必要的,因為垃圾回收機制無法檢測到外部的資源什么時候會被釋放。
池化連接 --- 僅關閉邏輯上的連接和這個連接所創建的邏輯 statement,底層的物理連接以及相關的 statement 不會被關閉。
PooledConnection.close --- 由連接池管理者調用。會關閉底層的物理連接以及所有相關的 statement。
應用層無法直接關閉一個已經池化的物理 statement,這是連接池管理器做的事情。PooledConnection.close 方法關閉物理連接以及所有的關聯 statement,釋放掉相關的資源。
應用層也無法直接控制 statement 應該如何被池化。一個池化的 statement 總是與一個 PooledConnection 相關聯的,ConnectionPoolDataSource 可以用來對池化做一些屬性設置。
如果連接池管理器支持 statement 池化,它必須實現 StatementEventListener 接口,然后將自己注冊為 PooledConnection 對象的監聽者。這個接口定義了以下兩個方法,用來監聽有可能發生在一個 PreparedStatement 對象上的兩種事件。
statementClosed --- 當與 PooledConnection 對象相關聯的邏輯 PreparedStatement 對象被關閉時觸發,也就是說,當應用層調用 PreparedStatement.close 方法時。
statementErrorOccurred --- 當 JDBC 驅動監測到 PreparedStatement 對象不可用時觸發。
連接池管理器通過 PooledConnection.addStatementEventListener 方法將自己注冊為監聽者。一般來說,在連接池管理器返回一個 PreparedStatement 對象給應用層使用之前,它必須先把自己注冊為一個監聽者。
當對應的事件發生時,驅動會調用 StatementEventListener 的 statementClosed 方法和 statementErrorOccurred 方法,這兩個方法都接收一個 statementEvent 對象作為參數,這個參數就可以用來判斷是發生了關閉事件還是異常事件。當 JDBC 應用關閉邏輯 statement ,或者一些錯誤發生時,JDBC 驅動會調用相關的方法,這個時候,連接池管理器它就可以將這個 statement 放回池子以便重用,或者是拋出異常。
JDBC 的 API 定義了一系列的屬性來設置與池化相關的屬性:
屬性名 | 類型 | 描述 |
---|---|---|
maxStatements | int | 允許池化的最大 statement 數,0 代表不池化 |
initialPoolSize | int | 當連接池創建時需要創建的初始物理連接數 |
minPoolSize | int | 連接池最小物理連接數 |
maxPoolSize | int | 連接池最大物理連接數,0代表無限制 |
maxIdleTime | int | 連接空閑最大空閑時間,0代表無限制 |
propertyCycle | int | 屬性生效時間,單位為秒 |
連接池的配置風格遵循 JavaBean 風格。連接池廠商如果需要增加配置屬性,那這些新增的屬性名不應與已有的標準屬性名重復。
與 DataSource 的實現一樣,ConnectionPoolDataSource 的實現也必須為每個屬性增加 setter 和 getter 方法,以下代碼是一個示例:
VendorConnectionPoolDS vcp = new VendorConnectionPoolDS(); vcp.setMaxStatements(25); vcp.setInitialPoolSize(10); vcp.setMinPoolSize(1); vcp.setMaxPoolSize(0); vcp.setMaxIdleTime(0); vcp.setPropertyCycle(300);
應用服務器會根據設置的屬性,來決定應該如何管理相關的池子。
ConnectionPoolDataSource 的配置屬性無須被 JDBC 客戶端直接訪問。一些管理工具需要訪問的話,建議通過反射的方式。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/72379.html
摘要:基于版本基于版本。由于中英行文差異,完全的逐字逐句翻譯會很冗余啰嗦。譯者在翻譯中同時參考了谷歌百度有道翻譯的譯文以及編程思想第四版中文版的部分內容對其翻譯死板,生造名詞,語言精煉度差問題進行規避和改正。 來源:LingCoder/OnJava8 主譯: LingCoder 參譯: LortSir 校對:nickChenyx E-mail: 本書原作者為 [美] Bru...
摘要:概述經過半年的搗鼓,終于將協議全篇翻譯完成。現在將所有章節全部整理到一篇文章中,方便大家閱讀。如果大家想看具體的翻譯文檔,可以去我的中查看。大家有相關類型的需要,建議大家可以嘗試下。 概述 經過半年的搗鼓,終于將 WebSocket 協議(RFC6455)全篇翻譯完成。現在將所有章節全部整理到一篇文章中,方便大家閱讀。如果大家想看具體的翻譯文檔,可以去我的GitHub中查看。 具體章節...
摘要:新書中文版發布。譯者聲明中文翻譯經過個多月,我們終于完成了翻譯草稿。注意各種問題或者建議可以提,建議使用中文。可用于學習研究目的,不得用于任何商業行為。 Yoshua Bengio 新書《Deep Learning》中文版發布。該書由北京大學張志華老師團隊負責翻譯。本書于學習研究目的,不得用于任何商業行為。下載鏈接:https://github.com/exacity/deeplearnin...
摘要:方法接受一個生產者作為參數,返回一個對象,該對象完成異步執行后會讀取調用生產者方法的返回值。該方法接收一個對象構成的數組,返回由第一個執行完畢的對象的返回值構成的。 一、Future 接口 在Future中觸發那些潛在耗時的操作把調用線程解放出來,讓它能繼續執行其他有價值的工作,不再需要呆呆等待耗時的操作完成。打個比方,你可以把它想象成這樣的場景:你拿了一袋子衣服到你中意的干洗店去洗。...
摘要:是一個系統支持的所有字符的集合,包括各國家文字標點符號圖形符號數字等字符集簡體中文碼表。支持中國國內少數民族的文字,同時支持繁體漢字以及日韓漢字等字符集為表達任意語言的任意字符而設計,是業界的一種標準,也稱為統一碼標準萬國碼。 1 File1.1 File類的概述和構造方法File: 它是文件和目錄路徑名的抽象...
閱讀 1937·2021-11-24 09:39
閱讀 3522·2021-09-28 09:36
閱讀 3291·2021-09-06 15:10
閱讀 3446·2019-08-30 15:44
閱讀 1159·2019-08-30 15:43
閱讀 1802·2019-08-30 14:20
閱讀 2719·2019-08-30 12:51
閱讀 2038·2019-08-30 11:04