详解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硬编码的形式,提高了代码复用性。
以上是关于详解C3P0(数据库连接池)的主要内容,如果未能解决你的问题,请参考以下文章