JDBC数据库连接池的必要性
一、在使用开发基于数据库的web程序时,传统的模式基本是按一下步骤:
1)在主程序(如servlet/beans)中建立数据库连接
2)进行sql操作
3)断开数据库连接
二、这种模式开发,存在的问题:
1)普通的JDBC数据库连接使用DriverManager来获取,每次向数据库建立连接的时候都要将Connection加载进内存中,再验证用户名和密码(得花费0.05s~1s的时间).需要数据库连接的时候,就向数据库要求一个,执行完成后就断开连接。这样的方式将消耗大连的时间和资源。数据库的连接资源并没有得到很好的重复利用。若同时有几百人甚至几千人同时在线,频繁的进行数据库连接将占用很多的系统资源,严重的甚至会造成服务器的崩溃。
2)对于每一次数据库连接,使用完成后都要断开。否则,如果程序出现异常而未能关闭,将导致数据库系统的内存泄露,并最终导致重启数据库。
3)这种开发不能控制被创建的连接对象数,系统资源被毫无顾忌的分配出去,如连接过多,也可能导致内存泄露,服务器崩溃
数据库连接池(connection pool)
1)为了解决传统开发中的数据库连接问题,可以采用数据库连接池技术。
2)数据库连接池的基本思想就是为数据库连接建立一个“缓冲池”,预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需要从“缓冲池”中取出一个,使用完毕之后再放回去。
3)数据库连接池负责分配、管理和释放数据库连接,他允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个。
4)数据库连接池在初始化的时候将创建一定数量的数据库连接放到连接池中,这些数据库连接的数量由最小数据库连接数来设定的。无论这些数据库连接是否被使用,连接池都将一直保证至少拥有这么多的连接数量。
连接池的最大数据库连接数量限定了这个连接池能占有的最大连接数,当应用程序想连接池请求的连接数超过最大连接数量时,这些请求将被加入到等待序列中.
数据库连接池(connection pool)的工作原理
数据库连接池(connection pool)的优点
1) 资源重用
由于数据库连接得到重用,避免了频繁创建、释放连接引起的大量性能开销。在减少系统消耗的基础上,另一方面也增进了系统运行环境的平稳性(减少内存碎片以及数据库临时进程/线程的数量);
2) 更快的系统响应速度
数据库连接池在初始化过程中,往往已经创建了若干数据库连接置于池中备用。此时连接的初始化工作均已完成。对于业务请求处理而言,直接利用现有可用连接,避免了数据库连接初始化和释放过程的时间开销,从而缩减了系统整体响应时间;
3) 新的资源分配手段
对于多应用共享同一数据库的系统而言,可在应用层通过数据库连接的配置,实现数据库连接池技术,几年前也许还是个新鲜话题,对于目前的业务系统而言,如果设计中还没有考虑到连接池的应用,那么…….快在设计文档中加上这部分的内容吧。某一应用最大可用数据库连接数的限制,避免某一应用独占所有数据库资源;
4) 统一的连接管理,避免数据库连接泄漏
在较为完备的数据库连接池实现中,可根据预先的连接占用超时设定,强制收回被占用连接。从而避免了常规数据库连接操作中可能出现的资源泄漏。一个最小化的数据库连接池实现;
* 1.加入JAR包(2个)
* commons-dbcp2-2.1.1.jar
* Commons-pool.jar
* 2.创建数据库连接池
* 3.为数据源实例指定必须的属性
* 4.从数据源中获取数据库连接
具体代码实现:
1 @Test 2 public void testDBCP() throws Exception{ 3 DataSource dataSource=null; 4 //1.创建DBCP数据源实例 5 dataSource=new BasicDataSource(); 6 //2.为数据源实例指定必须的属性 7 ((BasicDataSource) dataSource).setUsername("root"); 8 ((BasicDataSource) dataSource).setPassword("123456"); 9 ((BasicDataSource) dataSource).setUrl("jdbc:mysql://localhost:3306/atguigu"); 10 ((BasicDataSource) dataSource).setDriverClassName("com.mysql.jdbc.Driver"); 11 //3.指定数据源一些可选的属性(可以看下载的Jar包中的index.html)API文档 12 /* 13 * maxIdle,最大空闲数,数据库连接的最大空闲时间。超过空闲时间,数据库连接将被标记为不可用,然后被释放。设为0表示无限制。 14 MaxActive,连接池的最大数据库连接数。设为0表示无限制。 15 maxWait ,最大建立连接等待时间。如果超过此时间将接到异常。设为-1表示无限制 16 【温馨提示】:pool2中修改如下: 17 maxActive ==> maxTotal 18 maxWait ==> maxWaitMillis 19 */ 20 //1).指定数据库连接池中初始化连接的个数 21 ((BasicDataSource) dataSource).setInitialSize(10); 22 //2).指定数据库连接池中最大连接的个数:同一时刻可以同时向数据库申请的数据库连接 23 ((BasicDataSource) dataSource).setMaxTotal(5); 24 //3).指定数据库连接池中最小连接的个数:在连接池中保存的最小空闲连接的数量 25 ((BasicDataSource) dataSource).setMinIdle(5); 26 //4).指定数据库连接池分配连接的最长时间,单位为毫秒,超出改时间将抛出异常 27 ((BasicDataSource) dataSource).setMaxWaitMillis(1000*5); 28 //4.从数据源中获取数据库连接 29 30 Connection connection=dataSource.getConnection(); 31 System.out.println(connection.getClass()); 32 33 }
方式二:
使用DBCP数据库连接池的步骤
* 1.加载dbcp的properties配置文件
* 配置文件中的键值对需要来自BasicDataSource这个类的属性
* 2.调用BasicDataSourceFactory的createDataSource方法
* 创建DataSource实例
* 3.从DataSource中获取数据库连接
dbcp.properties文件内容如下:
driver=com.mysql.jdbc.Driver jdbcUrl=jdbc:mysql://localhost:3306/atguigu user=root password=123456 initialSize=10 maxTotal=50 minIdle=5 maxWaitMillis=5000
具体代码实现:
1 @Test 2 public void testDBCPWithDataSourceFactory() throws Exception{ 3 Properties properties=new Properties(); 4 InputStream inputStream=JDBCTest.class.getClassLoader() 5 .getResourceAsStream("dbcp.properties"); 6 properties.load(inputStream); 7 DataSource dataSource= 8 BasicDataSourceFactory.createDataSource(properties); 9 System.out.println(dataSource.getConnection()); 10 11 BasicDataSource basicDataSource= 12 (BasicDataSource) dataSource; 13 System.out.println(basicDataSource.getMaxTotal()); 14 15 }
C3P0数据库连接池
方式一:
这里我们需要加入两个JAR包:c3p0-0.9.5.2.jar和mchange-commons-java-0.2.11.jar
我们下载的c3p0JAR包中的doc文件夹中的index.html文件,点击该文件->Contents->Quickstart,我们可以看到c3p0数据库连接池的创建步骤,如下图所示(我们使用一些JAR包的时候要善于利用好帮助文档):
import com.mchange.v2.c3p0.*; ... ComboPooledDataSource cpds = new ComboPooledDataSource(); cpds.setDriverClass( "org.postgresql.Driver" ); //loads the jdbc driver cpds.setJdbcUrl( "jdbc:postgresql://localhost/testdb" ); cpds.setUser("dbuser"); cpds.setPassword("dbpassword");
于是我们的代码就可以这样写了:
1 @Test 2 public void testC3P0() throws Exception{ 3 ComboPooledDataSource cpds = new ComboPooledDataSource(); 4 cpds.setDriverClass( "com.mysql.jdbc.Driver" ); //loads the jdbc driver 5 cpds.setJdbcUrl( "jdbc:mysql://localhost:3306/atguigu" ); 6 cpds.setUser("root"); 7 cpds.setPassword("123456"); 8 System.out.println(cpds.getConnection()); 9 }
运行结果:证明创建成功
五月 09, 2016 4:35:56 下午 com.mchange.v2.log.MLog 信息: MLog clients using java 1.4+ standard logging. 五月 09, 2016 4:35:57 下午 com.mchange.v2.c3p0.C3P0Registry 信息: Initializing c3p0-0.9.5.2 [built 08-December-2015 22:06:04 -0800; debug? true; trace: 10] 五月 09, 2016 4:35:57 下午 com.mchange.v2.c3p0.impl.AbstractPoolBackedDataSource 信息: Initializing c3p0 pool... com.mchange.v2.c3p0.ComboPooledDataSource [ acquireIncrement -> 3, acquireRetryAttempts -> 30, acquireRetryDelay -> 1000, autoCommitOnClose -> false, automaticTestTable -> null, breakAfterAcquireFailure -> false, checkoutTimeout -> 0, connectionCustomizerClassName -> null, connectionTesterClassName -> com.mchange.v2.c3p0.impl.DefaultConnectionTester, contextClassLoaderSource -> caller, dataSourceName -> 1hge1d39g158wpld8tx485|1588809, debugUnreturnedConnectionStackTraces -> false, description -> null, driverClass -> com.mysql.jdbc.Driver, extensions -> {}, factoryClassLocation -> null, forceIgnoreUnresolvedTransactions -> false, forceSynchronousCheckins -> false, forceUseNamedDriverClass -> false, identityToken -> 1hge1d39g158wpld8tx485|1588809, idleConnectionTestPeriod -> 0, initialPoolSize -> 3, jdbcUrl -> jdbc:mysql://localhost:3306/atguigu, maxAdministrativeTaskTime -> 0, maxConnectionAge -> 0, maxIdleTime -> 0, maxIdleTimeExcessConnections -> 0, maxPoolSize -> 15, maxStatements -> 0, maxStatementsPerConnection -> 0, minPoolSize -> 3, numHelperThreads -> 3, preferredTestQuery -> null, privilegeSpawnedThreads -> false, properties -> {user=******, password=******}, propertyCycle -> 0, statementCacheNumDeferredCloseThreads -> 0, testConnectionOnCheckin -> false, testConnectionOnCheckout -> false, unreturnedConnectionTimeout -> 0, userOverrides -> {}, usesTraditionalReflectiveProxies -> false ] com.mchange.v2.c3p0.impl.NewProxyConnection@6776ad [wrapping: com.mysql.jdbc.JDBC4Connection@108b2d7]
方式二:使用配置文件的形式
具体的步骤:
* 1.创建c3p0-config.xml文件,参考帮助文档中 * Appendix B: Configuation Files, etc.的内容 * 2.创建ComboPooledDataSource实例: * DataSource dataSource = new ComboPooledDataSource("helloc3p0"); *3.从DataSource实例中获取数据库连接
其中c3p0-config.xml中的内容我们进行更改成下面的形式:每一行代表什么我都给出了详细的解释
<c3p0-config> <named-config name="helloc3p0"> <!-- 指定数据源的基本属性 --> <property name="user">root</property> <property name="password">123456</property> <property name="driverClass">com.mysql.jdbc.Driver</property> <property name="jdbcUrl">jdbc:mysql://localhost:3306/atguigu</property> <!-- 若数据库中连接数不足时,一次向数据库服务器申请多少个连接 --> <property name="acquireIncrement">5</property> <!-- 初始化数据库连接时连接的数量 --> <property name="initialPoolSize">10</property> <!-- 数据库连接池中最小的数据库连接数 --> <property name="minPoolSize">5</property> <!-- 数据库连接池中最大的数据库连接数 --> <property name="maxPoolSize">10</property> <!-- 数据库连接池可以维护的Statement的个数 --> <property name="maxStatements">10</property> <!-- 每个连接同时可以使用的Statement对象的个数 --> <property name="maxStatementsPerConnection">5</property> </named-config> </c3p0-config>
我们可以看到named-config name="helloc3p0",我们要创建的数据库连接池的名称是helloc3p0;
第二种方式的具体代码实现:
1 @Test 2 public void testC3P0withConfigFile() throws Exception{ 3 DataSource dataSource = 4 new ComboPooledDataSource("helloc3p0"); 5 System.out.println(dataSource.getConnection()); 6 ComboPooledDataSource comboPooledDataSource= 7 (ComboPooledDataSource) dataSource; 8 System.out.println(comboPooledDataSource.getMaxStatements()); 9 }
测试一些,运行结果:
1 五月 09, 2016 4:42:15 下午 com.mchange.v2.log.MLog 2 信息: MLog clients using java 1.4+ standard logging. 3 五月 09, 2016 4:42:15 下午 com.mchange.v2.c3p0.C3P0Registry 4 信息: Initializing c3p0-0.9.5.2 [built 08-December-2015 22:06:04 -0800; debug? true; trace: 10] 5 五月 09, 2016 4:42:15 下午 com.mchange.v2.c3p0.impl.AbstractPoolBackedDataSource 6 信息: Initializing c3p0 pool... com.mchange.v2.c3p0.ComboPooledDataSource [ acquireIncrement -> 5, acquireRetryAttempts -> 30, acquireRetryDelay -> 1000, autoCommitOnClose -> false, automaticTestTable -> null, breakAfterAcquireFailure -> false, checkoutTimeout -> 0, connectionCustomizerClassName -> null, connectionTesterClassName -> com.mchange.v2.c3p0.impl.DefaultConnectionTester, contextClassLoaderSource -> caller, dataSourceName -> helloc3p0, debugUnreturnedConnectionStackTraces -> false, description -> null, driverClass -> com.mysql.jdbc.Driver, extensions -> {}, factoryClassLocation -> null, forceIgnoreUnresolvedTransactions -> false, forceSynchronousCheckins -> false, forceUseNamedDriverClass -> false, identityToken -> 1hge1d39g1594tqaufcxlm|1588809, idleConnectionTestPeriod -> 0, initialPoolSize -> 10, jdbcUrl -> jdbc:mysql://localhost:3306/atguigu, maxAdministrativeTaskTime -> 0, maxConnectionAge -> 0, maxIdleTime -> 0, maxIdleTimeExcessConnections -> 0, maxPoolSize -> 10, maxStatements -> 10, maxStatementsPerConnection -> 5, minPoolSize -> 5, numHelperThreads -> 3, preferredTestQuery -> null, privilegeSpawnedThreads -> false, properties -> {user=******, password=******}, propertyCycle -> 0, statementCacheNumDeferredCloseThreads -> 0, testConnectionOnCheckin -> false, testConnectionOnCheckout -> false, unreturnedConnectionTimeout -> 0, userOverrides -> {}, usesTraditionalReflectiveProxies -> false ] 7 com.mchange.v2.c3p0.impl.NewProxyConnection@1bcffb5 [wrapping: com.mysql.jdbc.JDBC4Connection@c1f859] 8 10
此时我们JDBCtools中的获取连接的函数getConnection就可以改写代码了,不是每一次都创建新的数据库连接,而是从数据库连接池中获取连接,release函数也不是真正的关闭连接了,而是把数据库连接继续放到连接池中。
1 private static DataSource dataSource=null; 2 //数据库连接池只被初始化一次 3 static { 4 dataSource=new ComboPooledDataSource("helloc3p0"); 5 } 6 // 获取数据库连接 7 public static Connection getConnection() throws Exception{ 8 9 /* ClassNotFoundException, SQLException { 10 Properties properties = new Properties(); 11 InputStream inputStream = JDBCTest.class.getClassLoader() 12 .getResourceAsStream("jdbc.properties"); 13 properties.load(inputStream); 14 String user = properties.getProperty("user"); 15 String password = properties.getProperty("password"); 16 String jdbcUrl = properties.getProperty("jdbcUrl"); 17 String driverClass = properties.getProperty("driver"); 18 Class.forName(driverClass); 19 Connection connection = DriverManager.getConnection(jdbcUrl, user, 20 password);*/ 21 22 return dataSource.getConnection(); 23 }
数据库连接池一般只有一个,所以我们用static修饰,并使用静态代码块的形式初始化数据库连接池,getConnection中的被注释代码使我们原来的获取连接的方式,可以看到代码简洁了不少!