詳解C3P0(數據庫連接池)
一、基本定義
C3P0是一個開源的JDBC連接池,它實現了數據源與JNDI綁定,支持JDBC3規范和實現了JDBC2的標准擴展說明的Connection和Statement池的DataSources對象。
即將用於連接數據庫的連接整合在一起形成一個隨取隨用的數據庫連接池(Connection pool)。
二、使用C3P0(數據庫連接池)的必要性
當我們在進行基於數據庫的web程序開發時,我們可以先在主程序(如Servlet、Bean)中通過JDBC中的DriverManager建立數據庫連接,然后將要對數據庫進行操作的sql語句封裝到Statement中,最后在返回結果集后斷開數據庫連接。以上是較為傳統的開發模式,然而用這種模式開發會埋下嚴重的安全隱患。
1.JDBC傳統模式開發存在的主要問題
1.1>時間和內存資源消耗巨大
普通的JDBC數據庫連接使用DriverManager來獲取,每次向數據庫建立連接的時候都要將Connection加載到內存中,再根據JDBC代碼(或配置文件)中的用戶名和密碼進行驗證其正確性。這一過程一般會花費0.05~1s,一旦需要數據庫連接的時候就必須向數據庫請求一個,執行完后再斷開連接。顯然,如果同一個數據庫在同一時間有數十人甚至上百人請求連接勢必會占用大量的系統資源,嚴重的會導致服務器崩潰。
1.2>有內存泄漏的風險
因為每一次數據庫連接使用完后都需要斷開連接,但如果程序出現異常致使連接未能及時關閉,這樣就可能導致內存泄漏,最終只能以重啟數據庫的方法來解決;
另外使用傳統JDBC模式開發不能控制需要創建的連接數,系統一般會將資源大量分出給連接以防止資源不夠用,如果連接數超出一定數量也會有極大的可能導致內存泄漏。
三、數據庫連接池的詳細說明
為了解決由使用傳統開發模式創建連接導致的一系列問題,我們可以采用數據庫連接池技術。
數據庫連接池的基本原理就是為數據庫建立一個緩沖池。在緩沖池中先創建指定數量的數據庫連接,當有連接請求時就從緩沖池中取出處於“空閑”狀態的連接,並將此連接標記為“忙碌”,直到該請求進程結束后,它所使用的連接才會重新回到“空閑”狀態,並等待下一次請求調用。
從上面不難看出數據庫連接池的主要作用就是負責分配、管理和釋放數據庫連接,它允許程序重復使用同一個現有的數據庫連接,大大縮短了運行時間,提高了執行效率。
這里需要強調一點的是,數據庫連接池中的連接數是在其初始化時根據c3p0-config.xml中的最小連接數來確定的,關於c3p0-config.xml我會在后文提供模板以供大家參考。當然,無論連接池的連接數是否有被使用,它都至少會保持最小連接數,如果請求連接數超過最小連接數也會根據c3p0-config.xml中指定的自增長數增加連接數直到達到最大連接數,這時如果請求連接數量還是大於連接池中的連接數的話,剩下的請求將會被放入等待隊列直到有空閑連接出現。
這樣一來,數據庫連接池相較於傳統JDBC模式等到請求發出才創建連接的做法有着顯而易見的優勢。
四、使用連接池的明顯優勢
1.資源的高效利用
由於數據庫連接得以重用,避免了頻繁創建,釋放連接引起的大量性能開銷,減小了系統資源消耗的同時也提高了系統運行環境的平穩性。
2.更快的系統反應速度
數據庫連接池在初始化過程中,往往已經創建了若干數據庫連接置於連接池中備用。此時連接的初始化工作均已完成。對於業務請求處理而言,直接利用現有可用連接可以避免數據庫在連接初始化和釋放過程所需的時間開銷,從而減少了系統的響應時間,提高了系統的反應速度。
3.減少了資源獨占的風險
新的資源分配手段對於多應用共享同一數據庫的系統而言,可在應用層通過數據庫連接池的配置實現對某一應用最大可用數據庫連接數的限制,避免了應用獨占所有數據庫資源的風險。
4.統一的連接管理,避免數據庫連接泄露
在實現較為完善的數據庫連接池時,可根據預先的占用超時設定,強制回收被占用連接,從而避免了常規數據庫連接操作中可能出現的資源泄露。
——————————————我是一條優雅的分割線——————————————
雖然數據庫連接池(Connection pool)種類很多,並不僅限於c3p0這一個,像DBCP、BoneCP、Proxool、SmartPool、MiniConnectionPoolManager等等都是較為常用的,c3p0只是其中較為優秀且使用人數較多的一款,因為標題的原因這里只說c3p0。
五、C3P0實操
1.導入jar包
主要是c3p0和mysql,其他根據需求添加
2.配置xml文件
下面是我配置的c3p0-config.xml,可以作為模板以供大家參考:
3. 一般c3p0-config.xml模板
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
<?xml version=
"1.0"
encoding=
"UTF-8"
?>
<c3p0-config>
<
default
-config>
<!--mysql數據庫連接的各項參數-->
<property name=
"driverClass"
>com.mysql.jdbc.Driver</property>
<property name=
"jdbcUrl"
>jdbc:mysql:
//localhost:3306/day06</property>
<property name=
"user"
>root</property>
<property name=
"password"
>root</property>
<!--配置數據庫連接池的初始連接數、最小鏈接數、獲取連接數、最大連接數、最大空閑時間-->
<property name=
"initialPoolSize"
>
10
</property>
<property name=
"minPoolSize"
>
10
</property>
<property name=
"acquireIncrement"
>
5
</property>
<property name=
"maxPoolSize"
>
100
</property>
<property name=
"maxIdleTime"
>
30
</property>
</
default
-config>
</c3p0-config>
|
當然,除了以上這幾種常用的參數設置以外,這里還有一份有關c3p0-config.xml參數的詳細清單,如有需要可自行增加。
4.c3p0-config.xml參數清單
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
|
<c3p0-config>
<
default
-config>
<!--當連接池中的連接耗盡的時候c3p0一次同時獲取的連接數。Default:
3
-->
<property name=
"acquireIncrement"
>
3
</property>
<!--定義在從數據庫獲取新連接失敗后重復嘗試的次數。Default:
30
-->
<property name=
"acquireRetryAttempts"
>
30
</property>
<!--兩次連接中間隔時間,單位毫秒。Default:
1000
-->
<property name=
"acquireRetryDelay"
>
1000
</property>
<!--連接關閉時默認將所有未提交的操作回滾。Default:
false
-->
<property name=
"autoCommitOnClose"
>
false
</property>
<!--c3p0將建一張名為Test的空表,並使用其自帶的查詢語句進行測試。如果定義了這個參數那么
屬性preferredTestQuery將被忽略。你不能在這張Test表上進行任何操作,它將只供c3p0測試
使用。Default:
null
-->
<property name=
"automaticTestTable"
>Test</property>
<!--獲取連接失敗將會引起所有等待連接池來獲取連接的線程拋出異常。但是數據源仍有效
保留,並在下次調用getConnection()的時候繼續嘗試獲取連接。如果設為
true
,那么在嘗試
獲取連接失敗后該數據源將申明已斷開並永久關閉。Default:
false
-->
<property name=
"breakAfterAcquireFailure"
>
false
</property>
<!--當連接池用完時客戶端調用getConnection()后等待獲取新連接的時間,超時后將拋出
SQLException,如設為
0
則無限期等待。單位毫秒。Default:
0
-->
<property name=
"checkoutTimeout"
>
100
</property>
<!--通過實現ConnectionTester或QueryConnectionTester的類來測試連接。類名需制定全路徑。
Default: com.mchange.v2.c3p0.impl.DefaultConnectionTester-->
<property name=
"connectionTesterClassName"
></property>
<!--指定c3p0 libraries的路徑,如果(通常都是這樣)在本地即可獲得那么無需設置,默認
null
即可
Default:
null
-->
<property name=
"factoryClassLocation"
>
null
</property>
<!--強烈不建議使用該方法,將這個設置為
true
可能會導致一些微妙而奇怪的bug-->
<property name=
"forceIgnoreUnresolvedTransactions"
>
false
</property>
<!--每
60
秒檢查所有連接池中的空閑連接。Default:
0
-->
<property name=
"idleConnectionTestPeriod"
>
60
</property>
<!--初始化時獲取三個連接,取值應在minPoolSize與maxPoolSize之間。Default:
3
-->
<property name=
"initialPoolSize"
>
3
</property>
<!--最大空閑時間,
60
秒內未使用則連接被丟棄。若為
0
則永不丟棄。Default:
0
-->
<property name=
"maxIdleTime"
>
60
</property>
<!--連接池中保留的最大連接數。Default:
15
-->
<property name=
"maxPoolSize"
>
15
</property>
<!--JDBC的標准參數,用以控制數據源內加載的PreparedStatements數量。但由於預緩存的statements
屬於單個connection而不是整個連接池。所以設置這個參數需要考慮到多方面的因素。
如果maxStatements與maxStatementsPerConnection均為
0
,則緩存被關閉。Default:
0
-->
<property name=
"maxStatements"
>
100
</property>
<!--maxStatementsPerConnection定義了連接池內單個連接所擁有的最大緩存statements數。Default:
0
-->
<property name=
"maxStatementsPerConnection"
></property>
<!--c3p0是異步操作的,緩慢的JDBC操作通過幫助進程完成。擴展這些操作可以有效的提升性能
通過多線程實現多個操作同時被執行。Default:
3
-->
<property name=
"numHelperThreads"
>
3
</property>
<!--當用戶調用getConnection()時使root用戶成為去獲取連接的用戶。主要用於連接池連接非c3p0
的數據源時。Default:
null
-->
<property name=
"overrideDefaultUser"
>root</property>
<!--與overrideDefaultUser參數對應使用的一個參數。Default:
null
-->
<property name=
"overrideDefaultPassword"
>password</property>
<!--密碼。Default:
null
-->
<property name=
"password"
></property>
<!--定義所有連接測試都執行的測試語句。在使用連接測試的情況下這個一顯著提高測試速度。注意:
測試的表必須在初始數據源的時候就存在。Default:
null
-->
<property name=
"preferredTestQuery"
>select id from test where id=
1
</property>
<!--用戶修改系統配置參數執行前最多等待
300
秒。Default:
300
-->
<property name=
"propertyCycle"
>
300
</property>
<!--因性能消耗大請只在需要的時候使用它。如果設為
true
那么在每個connection提交的
時候都將校驗其有效性。建議使用idleConnectionTestPeriod或automaticTestTable
等方法來提升連接測試的性能。Default:
false
-->
<property name=
"testConnectionOnCheckout"
>
false
</property>
<!--如果設為
true
那么在取得連接的同時將校驗連接的有效性。Default:
false
-->
<property name=
"testConnectionOnCheckin"
>
true
</property>
<!--用戶名。Default:
null
-->
<property name=
"user"
>root</property>
<!--早期的c3p0版本對JDBC接口采用動態反射代理。在早期版本用途廣泛的情況下這個參數
允許用戶恢復到動態反射代理以解決不穩定的故障。最新的非反射代理更快並且已經開始
廣泛的被使用,所以這個參數未必有用。現在原先的動態反射與新的非反射代理同時受到
支持,但今后可能的版本可能不支持動態反射代理。Default:
false
-->
<property name=
"usesTraditionalReflectiveProxies"
>
false
</property>
</
default
-config>
</c3p0-config>
|
5.創建C3P0Util類
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
|
package
com.c3p0.utils;
import
java.sql.Connection;
import
java.sql.ResultSet;
import
java.sql.SQLException;
import
java.sql.Statement;
import
javax.sql.DataSource;
import
com.mchange.v2.c3p0.ComboPooledDataSource;
public
class
C3P0Util {
//使用ComboPooledDataSource來生成DataSource的實例
private
static
DataSource dataSource =
new
ComboPooledDataSource();
//從連接池中獲取連接
public
static
Connection getConnection() {
try
{
return
dataSource.getConnection();
}
catch
(SQLException e) {
// TODO Auto-generated catch block
throw
new
RuntimeException();
}
}
//釋放連接回連接池
public
static
void
release(Connection conn, Statement stmt, ResultSet rs) {
if
(rs !=
null
) {
try
{
rs.close();
}
catch
(Exception e) {
e.printStackTrace();
}
rs =
null
;
}
if
(stmt !=
null
) {
try
{
stmt.close();
}
catch
(Exception e) {
e.printStackTrace();
}
stmt =
null
;
}
if
(conn !=
null
) {
try
{
conn.close();
}
catch
(Exception e) {
e.printStackTrace();
}
conn =
null
;
}
}
}
|
6.創建user表和類
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
|
package
com.c3p0.utils;
import
java.io.Serializable;
import
java.util.Date;
public
class
User
implements
Serializable {
private
int
id;
private
String username;
private
String password;
private
String email;
private
Date birthday;
public
int
getId() {
return
id;
}
public
void
setId(
int
id) {
this
.id = id;
}
public
String getUsername() {
return
username;
}
public
void
setUsername(String username) {
this
.username = username;
}
public
String getPassword() {
return
password;
}
public
void
setPassword(String password) {
this
.password = password;
}
public
String getEmail() {
return
email;
}
public
void
setEmail(String email) {
this
.email = email;
}
public
Date getBirthday() {
return
birthday;
}
public
void
setBirthday(Date birthday) {
this
.birthday = birthday;
}
@Override
public
String toString() {
return
"User [id="
+ id +
", username="
+ username +
", password="
+ password +
", email="
+ email +
", birthday="
+ birthday
+
"]"
;
}
}
|
(注:后面的測試程序也是調用此表,我們就對表中的數據進行了封裝,后台數據庫也是此類結構,這里不再展示)
7.測試程序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
|
package
com.c3p0.utils;
import
java.sql.Connection;
import
java.sql.PreparedStatement;
import
java.sql.ResultSet;
import
java.sql.SQLException;
import
java.util.ArrayList;
import
java.util.List;
import
org.junit.Test;
public
class
TestCRUD {
@Test
public
void
testInsert() {
Connection conn =
null
;
PreparedStatement ps =
null
;
conn = C3P0Util.getConnection();
try
{
ps = conn.prepareStatement(
"INSERT INTO users (username,PASSWORD,email,birthday)VALUES('SUN99','123','123456@qq.com','2020-01-01')"
);
ps.executeUpdate();
System.out.println(
"添加操作執行成功!"
);
}
catch
(SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
System.out.println(
"添加操作執行失敗!"
);
}
finally
{
C3P0Util.release(conn, ps,
null
);
}
}
@Test
public
void
testSelect() {
Connection conn =
null
;
PreparedStatement ps =
null
;
ResultSet rs =
null
;
conn = C3P0Util.getConnection();
try
{
ps = conn.prepareStatement(
"Select * from users"
);
rs = ps.executeQuery();
List<User> list =
new
ArrayList<User>();
while
(rs.next()) {
User u =
new
User();
u.setId(rs.getInt(
1
));
u.setUsername(rs.getString(
2
));
u.setPassword(rs.getString(
3
));
u.setEmail(rs.getString(
4
));
u.setBirthday(rs.getDate(
5
));
list.add(u);
}
for
(User user : list) {
System.out.println(user.toString());
}
}
catch
(SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
finally
{
C3P0Util.release(conn, ps, rs);
}
}
@Test
public
void
testDelete() {
Connection conn =
null
;
PreparedStatement ps =
null
;
conn = C3P0Util.getConnection();
try
{
ps = conn.prepareStatement(
"delete from users where username='SUN99'"
);
ps.executeUpdate();
}
catch
(SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
finally
{
C3P0Util.release(conn, ps,
null
);
}
}
@Test
public
void
testUpdate() {
Connection conn =
null
;
PreparedStatement ps =
null
;
conn = C3P0Util.getConnection();
try
{
ps = conn.prepareStatement(
"UPDATE users SET username='SUN100',PASSWORD='456'WHERE id='1'"
);
}
catch
(SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
finally
{
C3P0Util.release(conn, ps,
null
);
}
}
}
|
六、學習總結
1.相較於JDBC,使用C3P0能夠更加高效地建立與數據庫的連接,尤其是在高並發隨機訪問數據庫的時候;
2.C3P0通過dataSource.getConnection()從線程池中獲取“空閑”連接,真正的數據庫連接創建與釋放則是由C3P0在后台自行完成的,我們只花費了獲取和釋放連接占用權的時間;
3.使用c3p0-config.xml代替原來JDBC硬編碼的形式,提高了代碼復用性。