c3p0 數據庫連接池
一直用c3p0很久了,但也沒時間或沒主動去研究過,直到最近頻頻在出現一些莫名其妙的問題,覺得還是有必要了解和研究一下。
- c3p0是什麽
c3p0的出現,是為了大大提高應用程序和數據庫之間訪問效率的。
它的特性:
- 編碼的簡單易用
- 連接的復用
- 連接的管理
說到c3p0,不得不說一下jdbc本身,c3p0願意就是對數據庫連接的管理,那麽原有的概念還是得清晰:DriverManager、Connection、StateMent、ResultMent。
jdbc:java database connective這套API,不用多說,是一套用於連接各式dbms或連接橋接器的api,兩個層級:上層供應用方調用api,下層,定義了各個dbms的spi的api(具體文檔見:這裏)。
主要要提的是:datasource、DriverManager,想到哪兒寫到哪兒,datasource是更高級一點的api,原因在於相對對應用來說更透明。
Connection:同dbms的邏輯鏈接,類似於session管理概念, SQL statements are executed and results are returned within the context of a connection.
jdbc的概念就到這裏,平時用得比較多。
- c3P0的概念
c3p0的bean配置如下:
1 <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close"> 2 <property name="driverClass" value="${jdbc.driverClassName}" /> 3 <property name="jdbcUrl" value="${jdbc.url}" /> 4 <property name="user" value="${jdbc.username}" /> 5 <property name="password" value="${jdbc.password}" /> 6 <property name="checkoutTimeout" value="30000" /> 7 <property name="maxPoolSize" value="15" /> 8 <property name="idleConnectionTestPeriod" value="180" /> 9 <property name="maxIdleTime" value="180" /> 10 </bean>
還有一些配置選項,後續詳細說明。可見c3p0的bean引用使用的是:ComboPooledDataSource,該類結構如下:
以上類圖都不是很完全,不過大體能表達出類之間的原理:
1、bean:ComboPooledDataSource的父類:AbstractPoolBackedDataSource有一個poolmanager字段,存儲著對pool管理器
2、獲取ds.getConnection()鏈接對象時,內部使用getPoolManger()獲取C3p0ConnectionPooledManager(mgr)對象,該manager管理著pool對象:C3P0PooledConnectionPool對象,mgr.getPool().checkoutPooledConnection()
3、自此該connection已經被獲取到了
4、讓我們看看該connection的真實面目吧:
ProxyConnection。
5、因此其實原理是:
從pool裏獲取到的connection,是proxy包裝的connection,而對connection的釋放或者重用,是pool的管理責任:初始化池大小,維護池的大小(expand或shrink),管理unused、expired、checkout、checkin連接。
真正底層的連接是jdbc自己的連接,而c3p0的管理部分,基本上使用的是synchronized關鍵字,使用timerTask定時器工作。
數據庫連接池C3P0框架是個非常優異的開源jar,高性能的管理著數據源,這裏只討論程序本身負責數據源,不討論容器管理。
一、實現方式:
C3P0有三種方式實現:
1.自己動手寫代碼,實現數據源
例如:在類路徑下配置一個屬性文件,config.properties,內容如下:
driverClass=xxx
jdbcUrl=xxx
user=xxx
password=xxx
...
然後代碼中實現
Properties props = new Properties();
InputStream in = Thread.class.getResourceAsStream("config.properties");
props.load(in);
in.close();
ComboPooledDataSource cpds = new ComboPooledDataSource();
cpds.setDriverClass(props.getProperty("driverClass"));
cpds.setJdbcUrl(props.getProperty("jdbcUrl"));
cpds.setUser(props.getProperty("user"));
cpds.setPassword(props.getProperty("password"));
...
這裏實現了一個數據源。
也可以這樣配置,在類路徑下配置一個xml文件,config.xml
<config>
<source name="source1">
<property name="user">root</property>
<property name="password">xxx</property>
<property name="url">xxx</property>
<property name="driverClass">xxx</property>
</source>
<source name="source2">
...
</source>
</config>
然後自己解析xml文件,這樣可以實現多個數據源的配置
2.配置默認的熟悉文件
類路徑下提供一個c3p0.properties文件(不能改名)
配置如下:
c3p0.driverClass=com.mysql.jdbc.Driver
c3p0.jdbcUrl=jdbc:mysql://localhost:3306/jdbc
c3p0.user=root
c3p0.password=java
... 上面只提供了最基本的配置項,其他配置項參照 文檔配置,記得是c3p0.後面加屬性名就是了,最後初始化數據源的方式就是這樣簡單: ... DataSource ds = new ComboPooledDataSource(); return ds; ... 然後就可以使用數據源了,C3P0會對c3p0.properties進行自動解析的 3.路徑下提供一個c3p0-config.xml文件
這種方式使用方式與第二種差不多,但是有更多的優點
(1).更直觀明顯,很類似hibernate和spring的配置
(2).可以為多個數據源服務,提供default-config和named-config兩種配置方式
<c3p0-config>
<default-config>
<property name="user">root</property>
<property name="password">java</property>
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/jdbc</property>
<property name="initialPoolSize">10</property>
<property name="maxIdleTime">30</property>
<property name="maxPoolSize">100</property>
<property name="minPoolSize">10</property>
</default-config>
<named-config name="mySource">
<property name="user">root</property>
<property name="password">java</property>
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/jdbc</property>
<property name="initialPoolSize">10</property>
<property name="maxIdleTime">30</property>
<property name="maxPoolSize">100</property>
<property name="minPoolSize">10</property>
</named-config>
</c3p0-config>
...
DataSource ds = new ComboPooledDataSource("mySource");
return ds;
...
這樣就可以使用數據源了。
二、部分參數配置說明:
1.最常用配置
initialPoolSize:連接池初始化時創建的連接數,default : 3(建議使用)
minPoolSize:連接池保持的最小連接數,default : 3(建議使用)
maxPoolSize:連接池中擁有的最大連接數,如果獲得新連接時會使連接總數超過這個值則不會再獲取新連接,而是等待其他連接釋放,所以這個值有可能會設計地很大,default : 15(建議使用)
acquireIncrement:連接池在無空閑連接可用時一次性創建的新數據庫連接數,default : 3(建議使用)
2.管理連接池的大小和連接的生存時間
maxConnectionAge:配置連接的生存時間,超過這個時間的連接將由連接池自動斷開丟棄掉。當然正在使用的連接不會馬上斷開,而是等待它close再斷開。配置為0的時候則不會對連接的生存時間進行限制。default : 0 單位 s(不建議使用)
maxIdleTime:連接的最大空閑時間,如果超過這個時間,某個數據庫連接還沒有被使用,則會斷開掉這個連接。如果為0,則永遠不會斷開連接,即回收此連接。default : 0 單位 s(建議使用)
maxIdleTimeExcessConnections:這個配置主要是為了快速減輕連接池的負載,比如連接池中連接數因為某次數據訪問高峰導致創建了很多數據連接,但是後面的時間段需要的數據庫連接數很少,需要快速釋放,必須小於maxIdleTime。其實這個沒必要配置,maxIdleTime已經配置了。default : 0 單位 s(不建議使用)
3.配置連接測試:
automaticTestTable:配置一個表名,連接池根據這個表名用自己的測試sql語句在這個空表上測試數據庫連接,這個表只能由c3p0來使用,用戶不能操作。default : null(不建議使用)
preferredTestQuery:與上面的automaticTestTable二者只能選一。自己實現一條SQL檢測語句。default : null(建議使用)
idleConnectionTestPeriod:用來配置測試空閑連接的間隔時間。測試方式還是上面的兩種之一,可以用來解決MySQL8小時斷開連接的問題。因為它保證連接池會每隔一定時間對空閑連接進行一次測試,從而保證有效的空閑連接能每隔一定時間訪問一次數據庫,將於MySQL8小時無會話的狀態打破。為0則不測試。default : 0(建議使用)
testConnectionOnCheckin:如果為true,則在close的時候測試連接的有效性。default : false(不建議使用)
testConnectionOnCheckout:性能消耗大。如果為true,在每次getConnection的時候都會測試,為了提高性能,盡量不要用。default : false(不建議使用)
4.配置PreparedStatement緩存:
maxStatements:連接池為數據源緩存的PreparedStatement的總數。由於PreparedStatement屬於單個Connection,所以這個數量應該根據應用中平均連接數乘以每個連接的平均PreparedStatement來計算。同時maxStatementsPerConnection的配置無效。default : 0(不建議使用)
maxStatementsPerConnection:連接池為數據源單個Connection緩存的PreparedStatement數,這個配置比maxStatements更有意義,因為它緩存的服務對象是單個數據連接,如果設置的好,肯定是可以提高性能的。為0的時候不緩存。default : 0(看情況而論)
5.重連相關配置
acquireRetryAttempts:連接池在獲得新連接失敗時重試的次數,如果小於等於0則無限重試直至連接獲得成功。default : 30(建議使用)
acquireRetryDelay:連接池在獲得新連接時的間隔時間。default : 1000 單位ms(建議使用)
breakAfterAcquireFailure:如果為true,則當連接獲取失敗時自動關閉數據源,除非重新啟動應用程序。所以一般不用。default : false(不建議使用)
checkoutTimeout:配置當連接池所有連接用完時應用程序getConnection的等待時間。為0則無限等待直至有其他連接釋放或者創建新的連接,不為0則當時間到的時候如果仍沒有獲得連接,則會拋出SQLException。其實就是acquireRetryAttempts*acquireRetryDelay。default : 0(與上面兩個,有重復,選擇其中兩個都行)
6.定制管理Connection的生命周期
connectionCustomizerClassName:用來定制Connection的管理,比如在Connection acquire 的時候設定Connection的隔離級別,或者在Connection丟棄的時候進行資源關閉,
就可以通過繼承一個AbstractConnectionCustomizer來實現相關方法,配置的時候使用全類名。有點類似監聽器的作用。default : null(不建議使用)
7.配置未提交的事務處理
autoCommitOnClose:連接池在回收數據庫連接時是否自動提交事務。如果為false,則會回滾未提交的事務,如果為true,則會自動提交事務。default : false(不建議使用)
forceIgnoreUnresolvedTransactions:這個配置強烈不建議為true。default : false(不建議使用)
一般來說事務當然由自己關閉了,為什麽要讓連接池來處理這種不細心問題呢?
8.配置debug和回收Connection
unreturnedConnectionTimeout:為0的時候要求所有的Connection在應用程序中必須關閉。如果不為0,則強制在設定的時間到達後回收Connection,所以必須小心設置,保證在回收之前所有數據庫操作都能夠完成。這種限制減少Connection未關閉情況的不是很適用。建議手動關閉。default : 0 單位 s(不建議使用)
debugUnreturnedConnectionStackTraces:如果為true並且unreturnedConnectionTimeout設為大於0的值,當所有被getConnection出去的連接unreturnedConnectionTimeout時間到的時候,就會打印出堆棧信息。只能在debug模式下適用,因為打印堆棧信息會減慢getConnection的速度default : false(不建議使用)
其他配置項:因為有些配置項幾乎沒有自己配置的必要,使用默認值就好,所以沒有再寫出來。
三、示例:
示例采用第二種方式:
1.c3p0.properties:
Java代碼
- #驅動
- c3p0.driverClass=com.mysql.jdbc.Driver
- #地址
- c3p0.jdbcUrl=jdbc:mysql://localhost:3306/jdbc
- #用戶名
- c3p0.user=root
- #密碼
- c3p0.password=lovejava
- #-------------------------------
- #連接池初始化時創建的連接數
- c3p0.initialPoolSize=3
- #連接池保持的最小連接數
- c3p0.minPoolSize=3
- #連接池在無空閑連接可用時一次性創建的新數據庫連接數,default:3
- c3p0.acquireIncrement=3
- #連接池中擁有的最大連接數,如果獲得新連接時會使連接總數超過這個值則不會再獲取新連接,而是等待其他連接釋放,所以這個值有可能會設計地很大,default : 15
- c3p0.maxPoolSize=15
- #連接的最大空閑時間,如果超過這個時間,某個數據庫連接還沒有被使用,則會斷開掉這個連接,單位秒
- c3p0.maxIdleTime=100
- #連接池在獲得新連接失敗時重試的次數,如果小於等於0則無限重試直至連接獲得成功
- c3p0.acquireRetryAttempts=30
- #連接池在獲得新連接時的間隔時間
- c3p0.acquireRetryDelay=1000
2.ConnectionPool
Java代碼
- package com.study.pool;
- import java.sql.Connection;
- import java.sql.SQLException;
- import javax.sql.DataSource;
- import com.mchange.v2.c3p0.ComboPooledDataSource;
- public class ConnectionPool {
- private DataSource ds;
- private static ConnectionPool pool;
- private ConnectionPool(){
- ds = new ComboPooledDataSource();
- }
- public static final ConnectionPool getInstance(){
- if(pool==null){
- try{
- pool = new ConnectionPool();
- }catch (Exception e) {
- e.printStackTrace();
- }
- }
- return pool;
- }
- public synchronized final Connection getConnection() {
- try {
- return ds.getConnection();
- } catch (SQLException e) {
- e.printStackTrace();
- }
- return null;
- }
- }
3.PoolThread
Java代碼
- package com.study.pool;
- import java.sql.Connection;
- import java.sql.PreparedStatement;
- import java.sql.ResultSet;
- import java.sql.SQLException;
- public class PoolThread extends Thread {
- @Override
- public void run(){
- ConnectionPool pool = ConnectionPool.getInstance();
- Connection con = null;
- PreparedStatement stmt= null;
- ResultSet rs = null;
- try{
- con = pool.getConnection();
- stmt = con.prepareStatement("select sysdate as nowtime from dual");
- rs = stmt.executeQuery();
- while(rs.next()){
- System.out.println(Thread.currentThread().getId()+"---------------開始"+rs.getString("nowtime"));
- }
- } catch (Exception e) {
- e.printStackTrace();
- }finally{
- try {
- rs.close();
- stmt.close();
- con.close();
- } catch (SQLException e) {
- e.printStackTrace();
- }
- }
- System.out.println(Thread.currentThread().getId()+"--------結束");
- }
- }
4.PoolMain
Java代碼
- package com.study.pool;
- public class PoolMain {
- /**
- * 數據源緩沖池 實例練習
- */
- public static void main(String[] args) {
- System.out.println("緩沖池模擬開始");
- PoolThread[] threads = new PoolThread[50];
- for(int i=0;i<threads.length;i++){
- threads[i] = new PoolThread();
- }
- for(int i=0;i<threads.length;i++){
- threads[i].start();
- }
- }
- }
c3p0 數據庫連接池