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