對於共享資源,有一個很著名的設計模式:資源池(Resource Pool)。該模式正是爲了解決資源的頻繁分配﹑釋放所形成的問題。爲解決咱們的問題,能夠採用數據庫鏈接池技術。數據庫鏈接池的基本思想就是爲數據庫鏈接創建一個「緩衝池」。預先在緩衝池中放入必定數量的鏈接,當須要創建數據庫鏈接時,只需從「緩衝池」中取出一個,使用完畢以後再放回去。咱們能夠經過設定鏈接池最大鏈接數來防止系統無盡的與數據庫鏈接。更爲重要的是咱們能夠經過鏈接池的管理機制監視數據庫的鏈接的數量﹑使用狀況,爲系統開發﹑測試及性能調整提供依據。html
鏈接,是咱們的編程語言與數據庫交互的一種方式。咱們常常會聽到這麼一句話「數據庫鏈接很昂貴「。java
有人接受這種說法,殊不知道它的真正含義。所以,下面經過實例解釋它到底是什麼。mysql
下面是Mysql數據庫建立鏈接的的一段代碼:web
[java]sql
String connUrl ="jdbc:mysql://your.database.domain/yourDBname"; 數據庫
Class.forName("com.mysql.jdbc.Driver"); apache
Connection con =DriverManager.getConnection (connUrl); 編程
當咱們建立了一個Connection對象,它在內部都執行了什麼:c#
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萬次鏈接,極大的浪費數據庫的資源,而且極易形成數據庫服務器內存溢出、拓機。
採用鏈接池技術後的過程以下:
數據庫鏈接是一種關鍵的有限的昂貴的資源,這一點在多用戶的網頁應用程序中體現的尤其突出。對數據庫鏈接的管理能顯著影響到整個應用程序的伸縮性和健壯性,影響到程序的性能指標。數據庫鏈接池負責分配,管理和釋放數據庫鏈接,它容許應用程序重複使用一個現有的數據庫鏈接,而不是從新創建一個。
一、併發問題
爲了使鏈接管理服務具備最大的通用性,必須考慮多線程環境,即併發問題。這個問題相對比較好解決,由於各個語言自身提供了對併發管理的支持像java,c#等等,使用synchronized(java)、lock(C#)關鍵字便可確保線程是同步的。
二、事務處理
咱們知道,事務具備原子性,此時要求對數據庫的操做符合「ALL-OR-NOTHING」原則,即對於一組SQL語句要麼全作,要麼全不作。
咱們知道當2個線程公用一個鏈接Connection對象,並且各自都有本身的事務要處理時候,對於鏈接池是一個很頭疼的問題,由於即便Connection類提供了相應的事務支持,但是咱們仍然不能肯定那個數據庫操做是對應那個事務的,這是因爲咱們有2個線程都在進行事務操做而引發的。爲此咱們可使用每個事務獨佔一個鏈接來實現,雖然這種方法有點浪費鏈接池資源可是能夠大大下降事務管理的複雜性。
3、鏈接池的分配與釋放
鏈接池的分配與釋放,對系統的性能有很大的影響。合理的分配與釋放,能夠提升鏈接的複用度,從而下降創建新鏈接的開銷,同時還能夠加快用戶的訪問速度。
對於鏈接的管理可以使用一個List。即把已經建立的鏈接都放入List中去統一管理。每當用戶請求一個鏈接時,系統檢查這個List中有沒有能夠分配的鏈接。若是有就把那個最合適的鏈接分配給他(如何能找到最合適的鏈接文章將在關鍵議題中指出);若是沒有就拋出一個異常給用戶,List中鏈接是否能夠被分配由一個線程來專門管理。
4、鏈接池的配置與維護
鏈接池中到底應該放置多少鏈接,才能使系統的性能最佳?系統可採起設置最小鏈接數(minConnection)和最大鏈接數(maxConnection)等參數來控制鏈接池中的鏈接。比方說,最小鏈接數是系統啓動時鏈接池所建立的鏈接數。若是建立過多,則系統啓動就慢,但建立後系統的響應速度會很快;若是建立過少,則系統啓動的很快,響應起來卻慢。這樣,能夠在開發時,設置較小的最小鏈接數,開發起來會快,而在系統實際使用時設置較大的,由於這樣對訪問客戶來講速度會快些。最大鏈接數是鏈接池中容許鏈接的最大數目,具體設置多少,要看系統的訪問量,可經過軟件需求上獲得。
如何確保鏈接池中的最小鏈接數呢?有動態和靜態兩種策略。動態即每隔必定時間就對鏈接池進行檢測,若是發現鏈接數量小於最小鏈接數,則補充相應數量的新鏈接,以保證鏈接池的正常運轉。靜態是發現空閒鏈接不夠時再去檢查。
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:將上面的配置內容直接添加在<Host>節點下。
方法2:在<GlobalNamingResources>節點下添加:
<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]
<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資源。
<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]
//得到對數據源的引用:
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 是 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)的靜態代碼塊中建立池:
[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;
/**
* 在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:
在應用中獲取鏈接
[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是一個開源的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數據庫鏈接池
/*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:
在應用中獲取鏈接
[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。
可根據項目的實際須要來選擇鏈接池。