對于共享資源,有一個很著名的設計模式:資源池(Resource Pool)。該模式正是為了解決資源的頻繁配置設定﹑釋放所造成的問題。為解決我們的問題,可以采用資料庫連接配接池技術。資料庫連接配接池的基本思想就是為資料庫連接配接建立一個“緩沖池”。預先在緩沖池中放入一定數量的連接配接,當需要建立資料庫連接配接時,隻需從“緩沖池”中取出一個,使用完畢之後再放回去。我們可以通過設定連接配接池最大連接配接數來防止系統無盡的與資料庫連接配接。更為重要的是我們可以通過連接配接池的管理機制監視資料庫的連接配接的數量﹑使用情況,為系統開發﹑測試及性能調整提供依據。
為什麼使用連接配接池
連接配接,是我們的程式設計語言與資料庫互動的一種方式。我們經常會聽到這麼一句話“資料庫連接配接很昂貴“。
有人接受這種說法,卻不知道它的真正含義。是以,下面通過執行個體解釋它究竟是什麼。
下面是Mysql資料庫建立連接配接的的一段代碼:
[java] view plaincopyprint?
- String connUrl ="jdbc:mysql://your.database.domain/yourDBname";
- Class.forName("com.mysql.jdbc.Driver");
- Connection con =DriverManager.getConnection (connUrl);
當我們建立了一個Connection對象,它在内部都執行了什麼:
1.“DriverManager”檢查并注冊驅動程式;
2.“com.mysql.jdbc.Driver”就是我們注冊了的驅動程式,它會在驅動程式類中調用“connect(url…)”方法。
3.com.mysql.jdbc.Driver的connect方法根據我們請求的“connUrl”,建立一個“Socket連接配接”,連接配接到IP為“your.database.domain”,預設端口3306的資料庫。
4.建立的Socket連接配接将被用來查詢我們指定的資料庫,并最終讓程式傳回得到一個結果。
簡單的擷取一個連接配接,系統卻要在背後做很多消耗資源的事情,大多時候,建立連接配接的時間比執行sql語句的時間還要長。
傳統的擷取連接配接方式如下圖所示:

使用者每次請求都需要向資料庫獲得連結,而資料庫建立連接配接通常需要消耗相對較大的資源,建立時間也較長。假設網站一天10萬通路量,資料庫伺服器就需要建立10萬次連接配接,極大的浪費資料庫的資源,并且極易造成資料庫伺服器記憶體溢出、拓機。
采用連接配接池技術後的過程如下:
資料庫連接配接是一種關鍵的有限的昂貴的資源,這一點在多使用者的網頁應用程式中展現的尤為突出。對資料庫連接配接的管理能顯著影響到整個應用程式的伸縮性和健壯性,影響到程式的性能名額。資料庫連接配接池負責配置設定,管理和釋放資料庫連接配接,它允許應用程式重複使用一個現有的資料庫連接配接,而不是重建立立一個。
需要注意的問題
1、并發問題
為了使連接配接管理服務具有最大的通用性,必須考慮多線程環境,即并發問題。這個問題相對比較好解決,因為各個語言自身提供了對并發管理的支援像java,c#等等,使用synchronized(java)、lock(C#)關鍵字即可確定線程是同步的。
2、事務處理
我們知道,事務具有原子性,此時要求對資料庫的操作符合“ALL-OR-NOTHING”原則,即對于一組SQL語句要麼全做,要麼全不做。
我們知道當2個線程公用一個連接配接Connection對象,而且各自都有自己的事務要處理時候,對于連接配接池是一個很頭疼的問題,因為即使Connection類提供了相應的事務支援,可是我們仍然不能确定那個資料庫操作是對應那個事務的,這是由于我們有2個線程都在進行事務操作而引起的。為此我們可以使用每一個事務獨占一個連接配接來實作,雖然這種方法有點浪費連接配接池資源但是可以大大降低事務管理的複雜性。
3、連接配接池的配置設定與釋放
連接配接池的配置設定與釋放,對系統的性能有很大的影響。合理的配置設定與釋放,可以提高連接配接的複用度,進而降低建立新連接配接的開銷,同時還可以加快使用者的通路速度。
對于連接配接的管理可使用一個List。即把已經建立的連接配接都放入List中去統一管理。每當使用者請求一個連接配接時,系統檢查這個List中有沒有可以配置設定的連接配接。如果有就把那個最合适的連接配接配置設定給他(如何能找到最合适的連接配接文章将在關鍵議題中指出);如果沒有就抛出一個異常給使用者,List中連接配接是否可以被配置設定由一個線程來專門管理。
4、連接配接池的配置與維護
連接配接池中到底應該放置多少連接配接,才能使系統的性能最佳?系統可采取設定最小連接配接數(minConnection)和最大連接配接數(maxConnection)等參數來控制連接配接池中的連接配接。比方說,最小連接配接數是系統啟動時連接配接池所建立的連接配接數。如果建立過多,則系統啟動就慢,但建立後系統的響應速度會很快;如果建立過少,則系統啟動的很快,響應起來卻慢。這樣,可以在開發時,設定較小的最小連接配接數,開發起來會快,而在系統實際使用時設定較大的,因為這樣對通路客戶來說速度會快些。最大連接配接數是連接配接池中允許連接配接的最大數目,具體設定多少,要看系統的通路量,可通過軟體需求上得到。
如何確定連接配接池中的最小連接配接數呢?有動态和靜态兩種政策。動态即每隔一定時間就對連接配接池進行檢測,如果發現連接配接數量小于最小連接配接數,則補充相應數量的新連接配接,以保證連接配接池的正常運轉。靜态是發現空閑連接配接不夠時再去檢查。
Tomcat連接配接池
Tomcat預設使用的是DBCP資料庫連接配接池,其實從本質上講,Tomcat是利用Apache Commons DBCP來實作的,隻不過把特定的功能內建到了tomcat-dbcp.jar包中。
使用法法如下:
步驟1:
在Tomcat中Context.xml中添加
[html] view plaincopyprint?
- <!-- path表示站點的通路方式 -->
- <!-- 例:http://localhost:8080/test 配置為/test -->
- <!-- docBase="fileLocation" 應用存儲的實際路徑,沒有的話則從webapps目錄找 -->
- <!-- Context标簽内的這些屬性都可以省略不寫,使用預設的設定 -->
- <Context path="/TomcatDbPools" docBase="TomcatDbPools" debug="0" reloadable="true">
- <!-- 使用DBCP配置的資料源 -->
- <Resource
- <!-- 指定資源池的Resource的JNDI的名字,就是給連接配接池起的名字 -->
- name="jdbc/mysql_connect"
- <!-- 管理權限,指定管理Resource的Manager,可以是Container或Application -->
- auth="Container"
- <!--指出Resource所屬的類名,是什麼類型的資料源-->
- type="javax.sql.DataSource"
- <!-- 資料庫驅動類 -->
- driverClassName="com.mysql.jdbc.Driver"
- <!-- 資料庫連接配接url-->
- url=" jdbc:mysql://localhost:3306/test"
- <!-- 資料庫使用者名 -->
- username="admin"
- <!-- 資料庫密碼 -->
- password="123456"
- <!-- 連接配接池最大激活的連接配接數,設為0表示無限制-->
- maxActive="100"
- <!-- 連接配接池中最多可空閑的連接配接數 -->
- maxIdle="30"
- <!-- 為連接配接最大的等待時間,機關毫秒,如果超過此時間将接到異常。設為-1表示無限制-->
- maxWait="10000" />
- </context>
注:還可以用minIdle配置連接配接池中最少空閑maxIdle個連接配接,用initialSize配置初始化連接配接數目。可同時配置多個資料源。
如果在Tomcat的server.xml檔案中配置資料源,有兩種方法都可以實作:
方法1:将上面的配置内容直接添加在<Host>節點下。
方法2:在<GlobalNamingResources>節點下添加:
[html] view plaincopyprint?
- <GlobalNamingResources>
- <!-- 這裡的factory指的是該Resource 配置使用的是哪個資料源配置類,這裡使用的是tomcat自帶的标準資料源Resource配置類,-->
- <!-- 這個類也可以自己寫,實作javax.naming.spi.ObjectFactory 接口即可。 -->
- <!-- 某些地方使用的commons-dbcp.jar中的org.apache.commons.dbcp.BasicDataSourceFactory,-->
- <!-- 如果使用這個就需把commons-dbcp.jar及其依賴的jar包,都放在tomcat的lib下,光放在工程的WEB-INF/lib下是不夠的。 -->
- <Resource
- name="mysql_connect"
- factory="org.apache.tomcat.dbcp.dbcp.BasicDataSourceFactory"
- maxActive="100"
- maxIdle="30"
- maxWait="10000"
- name="jdbc/TomcatDbPool1"
- password="123456"
- type="javax.sql.DataSource"
- url="jdbc:mysql://localhost:3306/test"
- username="root"/>
- </GlobalNamingResources>
然後在context.xml檔案中的<Context></Context>節點中加入如下内容:
[html] view plaincopyprint?
- <ResourceLink name="jdbc/mysql_connect" global="mysql_connect" type="javax.sql.DataSource"/>
在server.xml中配置的資料源是全局的,所有項目都可以使用。全局的resource隻是為了重用,友善所有該tomcat下的web工程的資料源管理,但如果你的tomcat不會同時加載多個web工程,也就是說一個tomcat隻加載一個web工程時,是沒有必要配置全局的resource的。
此外,還需要将mysql的Java驅動類以及其他依賴包(如果有)放到tomcat的lib目錄下。
步驟2:
在web.xml中,配置<resource-ref>元素以在web應用中引用JNDI資源。
[html] view plaincopyprint?
- <resource-ref>
- <!-- 對該資源的描述語言 -->
- <description> dbcpconnect</description>
- <!-- 引用的資源名,必須與Context.xml中的名字一緻 -->
- <res-ref-name> jdbc/mysql_connect </res-ref-name>
- <!-- 資源類型 -->
- <res-type>javax.sql.DataSource</res-type>
- <!-- 管理權限 -->
- <res-auth>Container</res-auth>
- </resource-ref>
步驟3:
在Web應用中使用資料源
[java] view plaincopyprint?
- //獲得對資料源的引用:
- Context ctx =new InitialContext();
- //java:comp/env/是java中JNDI固定寫法。
- DataSource ds =(DataSource) ctx.lookup("java:comp/env/jdbc/mysql_connect ");
- //獲得資料庫連接配接對象:
- Connection conn= ds.getConnection();
- //傳回資料庫連接配接到連接配接池:
- conn.close();
DBCP連接配接池
DBCP 是 Apache 軟體基金組織下的開源連接配接池實作,要使用DBCP資料源,需要應用程式應在系統中增加如下兩個 jar 檔案:
Commons-dbcp.jar:連接配接池的實作
Commons-pool.jar:連接配接池實作的依賴庫
Tomcat 的連接配接池正是采用該連接配接池來實作的。該資料庫連接配接池既可以與應用伺服器整合使用,也可由應用程式獨立使用。
步驟1:
在類目錄下加入dbcp的配置檔案:dbcp.properties
[plain] view plaincopyprint?
- #資料庫驅動
- driverClassName=com.mysql.jdbc.Driver
- #資料庫連接配接位址
- url=jdbc:mysql://localhost/test
- #使用者名
- username=root
- #密碼
- password=123456
- #連接配接池的最大資料庫連接配接數。設為0表示無限制
- maxActive=30
- #最大空閑數,資料庫連接配接的最大空閑時間。超過空閑時間,資料庫連
- #接将被标記為不可用,然後被釋放。設為0表示無限制
- maxIdle=10
- #最大建立連接配接等待時間。如果超過此時間将接到異常。設為-1表示無限制
- maxWait=1000
- #超過removeAbandonedTimeout時間後,是否進行沒用連接配接(廢棄)的回收(預設為false,調整為true)
- removeAbandoned=true
- #超過時間限制,回收沒有用(廢棄)的連接配接(預設為 300秒)
- removeAbandonedTimeout=180
步驟2:
在擷取資料庫連接配接的工具類(如jdbcUtils)的靜态代碼塊中建立池:
[java] view plaincopyprint?
- import java.io.InputStream;
- import java.sql.Connection;
- import java.sql.ResultSet;
- import java.sql.SQLException;
- import java.sql.Statement;
- import java.util.Properties;
- import javax.sql.DataSource;
- import org.apache.commons.dbcp.BasicDataSourceFactory;
- public classJdbcUtils_DBCP {
- private static DataSource ds = null;
- //在靜态代碼塊中建立資料庫連接配接池
- static{
- try{
- //加載dbcp.properties配置檔案
- InputStream in =JdbcUtils_DBCP.class.getClassLoader().getResourceAsStream("dbcp.properties");
- Properties prop = new Properties();
- prop.load(in);
- //建立資料源
- ds =BasicDataSourceFactory.createDataSource(prop);
- }catch (Exception e) {
- throw newExceptionInInitializerError(e);
- }
- }
- //從資料源中擷取資料庫連接配接
- public static Connection getConnection()throws SQLException{
- //從資料源中擷取資料庫連接配接
- return ds.getConnection();
- }
- //釋放連接配接
- public static void release(Connection conn){
- if(conn!=null){
- try{
- //将Connection連接配接對象還給資料庫連接配接池
- conn.close();
- }catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
- }
步驟3:
在應用中擷取連接配接
[java] view plaincopyprint?
- Connection conn = null;
- PreparedStatement st = null;
- ResultSet rs = null;
- try{
- //擷取資料庫連接配接
- conn =JdbcUtils_DBCP.getConnection();
- ……
- }catch (Exception e) {
- e.printStackTrace();
- }finally{
- //釋放資源
- JdbcUtils_DBCP.release(conn);
- }
C3P0連接配接池
c3p0是一個開源的JDBC連接配接池,它實作了資料源和JNDI綁定,支援JDBC3規範和JDBC2的标準擴充。c3p0一般是與Hibernate,Spring等架構一塊使用的,當然也可以單獨使用。
dbcp沒有自動回收空閑連接配接的功能,c3p0有自動回收空閑連接配接功能。
使用c3p0需要導入c3p0.jar、mchange-commons-.jar,如果操作的是Oracle資料庫,那麼還需要導入c3p0-oracle-thin-extras-pre1.jar。
步驟1:
在類目錄下加入C3P0的配置檔案:c3p0-config.xml
[html] view plaincopyprint?
- <c3p0-config>
- <!-- C3P0的預設(預設)配置,-->
- <!-- 如果在代碼中“ComboPooledDataSourceds = new ComboPooledDataSource();”這樣寫就表示使用的是C3P0的預設(預設)配置資訊來建立資料源 -->
- <default-config>
- <property name="driverClass">com.mysql.jdbc.Driver</property>
- <property name="jdbcUrl">jdbc:mysql://localhost:3306/anysearch</property>
- <property name="user">root</property>
- <property name="password">123456</property>
- <!--當連接配接池中的連接配接耗盡的時候c3p0一次同時擷取的連接配接數。Default:3 -->
- <property name="acquireIncrement">5</property>
- <!--初始化的連接配接數,取值應在minPoolSize與maxPoolSize之間。Default: 3-->
- <property name="initialPoolSize">10</property>
- <!--連接配接池中保留的最小連接配接數-->
- <property name="minPoolSize">5</property>
- <!--連接配接池中保留的最大連接配接數。Default:15 -->
- <property name="maxPoolSize">20</property>
- <!--定義在從資料庫擷取新連接配接失敗後重複嘗試的次數。Default: 30 -->
- <property name="acquireRetryAttempts">30</property>
- <!--兩次連接配接中間隔時間,機關毫秒。Default: 1000 -->
- <property name="acquireRetryDelay">1000</property>
- <!--連接配接關閉時預設将所有未送出的操作復原。Default: false -->
- <property name="autoCommitOnClose">false</property>
- </default-config>
- <!-- C3P0的命名配置,-->
- <!-- 如果在代碼中“ComboPooledDataSourceds = new ComboPooledDataSource("MySQL");”這樣寫就表示使用的是name是MySQL的配置資訊來建立資料源 -->
- <named-config name="MySQL">
- <property name="driverClass">com.mysql.jdbc.Driver</property>
- <property name="jdbcUrl">jdbc:mysql://localhost:3306/test2</property>
- <property name="user">root</property>
- <property name="password">123456</property>
- <property name="acquireIncrement">5</property>
- <property name="initialPoolSize">10</property>
- <property name="minPoolSize">5</property>
- <property name="maxPoolSize">20</property>
- </named-config>
- </c3p0-config>
還有更多可設定的參數,具體可查閱相關資料。
步驟2:
在擷取資料庫連接配接的工具類(如jdbcUtils)的靜态代碼塊中建立池
[java] view plaincopyprint?
- import java.sql.Connection;
- import java.sql.ResultSet;
- import java.sql.SQLException;
- import java.sql.Statement;
- import com.mchange.v2.c3p0.ComboPooledDataSource;
- public class JdbcUtils_C3P0 {
- private static ComboPooledDataSource ds =null;
- //在靜态代碼塊中建立資料庫連接配接池
- static{
- try{
- //通過代碼建立C3P0資料庫連接配接池
- //通過讀取C3P0的xml配置檔案建立資料源,C3P0的xml配置檔案c3p0-config.xml必須放在src目錄下
- //ds = newComboPooledDataSource();//使用C3P0的預設配置來建立資料源
- ds = newComboPooledDataSource("MySQL");//使用C3P0的命名配置來建立資料源
- }catch (Exception e) {
- throw newExceptionInInitializerError(e);
- }
- }
- //從資料源中擷取資料庫連接配接
- public static Connection getConnection()throws SQLException{
- //從資料源中擷取資料庫連接配接
- return ds.getConnection();
- }
- //釋放連結
- public static void release(Connection conn){
- if(conn!=null){
- try{
- //将Connection連接配接對象還給資料庫連接配接池
- conn.close();
- }catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
- }
步驟3:
在應用中擷取連接配接
[java] view plaincopyprint?
- Connection conn = null;
- PreparedStatement st = null;
- ResultSet rs = null;
- try{
- //擷取資料庫連接配接
- conn = JdbcUtils_C3P0.getConnection();
- ……
- }catch (Exception e) {
- e.printStackTrace();
- }finally{
- //釋放資源
- JdbcUtils_C3P0release(conn);
- }
其他連接配接池
此外,還有其他的連接配接池可供選擇,比如使用比較廣泛的Proxool。Proxool是一種Java資料庫連接配接池技術。是sourceforge下的一個開源項目,這個項目提供一個健壯、易用的連接配接池,最為關鍵的是這個連接配接池提供監控的功能,友善易用,便于發現連接配接洩漏的情況。
proxool和 c3p0能夠更好的支援高并發,但是在穩定性方面略遜于dpcp。
可根據項目的實際需要來選擇連接配接池。