前言:
對於共享資源,有一個很著名的設計模式:資源池(Resource Pool)。該模式正是爲了解決資源的頻繁分配﹑釋放所造成的問題。爲解決我們的問題,可以採用數據庫連接池技術。數據庫連接池的基本思想就是爲數據庫連接建立一個“緩衝池”。預先在緩衝池中放入一定數量的連接,當需要建立數據庫連接時,只需從“緩衝池”中取出一個,使用完畢之後再放回去。我們可以通過設定連接池最大連接數來防止系統無盡的與數據庫連接.更爲重要的是我們可以通過連接池的管理機制監視數據庫的連接的數量﹑使用情況,爲系統開發﹑測試及性能調整提供依據。
爲什麼使用連接池
連接,是我們的編程語言與數據庫交互的一種方式。我們經常會聽到這麼一句話“數據庫連接很昂貴“。
有人接受這種說法,卻不知道它的真正含義。因此,下面通過實例解釋它究竟是什麼。
下面是Mysql數據庫創建連接的的一段代碼:
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中添加
<!-- 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:將上面的配置內容直接添加在節點下。
方法2:在節點下添加:
<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中,配置元素以在web應用中引用JNDI資源。
<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應用中使用數據源
//獲得對數據源的引用:
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
#數據庫驅動
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)的靜態代碼塊中創建池:
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;
/**
* 在java中,編寫數據庫連接池需實現java.sql.DataSource接口,每一種數據庫連接池都是DataSource接口的實現
* DBCP連接池就是java.sql.DataSource接口的一個具體實現
*/
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:
在應用中獲取連接
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
<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)的靜態代碼塊中創建池
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數據庫連接池
/*ds = new ComboPooledDataSource();
ds.setDriverClass("com.mysql.jdbc.Driver");
ds.setJdbcUrl("jdbc:mysql://localhost:3306/test");
ds.setUser("root");
ds.setPassword("123456");
ds.setInitialPoolSize(10);
ds.setMinPoolSize(5);
ds.setMaxPoolSize(20);*/
//通過讀取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:
在應用中獲取連接
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。
可根據項目的實際需要來選擇連接池。
後記:
爲原創作者點個大讚,寫的真是太好了,解答了我再很多問題上的疑問,如果正在看的小夥伴有什麼疑問的,歡迎提出共同交流。
附上原文作者鏈接:
冰河:http://www.javaweb1024.com/java/JavaWebzhongji/2015/06/01/736.html