Java数据库连接池详解

http://www.javaweb1024.com/java/JavaWebzhongji/2015/06/01/736.html

 

对于共享资源,有一个很著名的设计模式:资源池(Resource Pool)。该模式正是为了解决资源的频繁分配﹑释放所造成的问题。为解决我们的问题,可以采用数据库连接池技术。数据库连接池的基本思想就是为数据库连接建立一个“缓冲池”。预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从“缓冲池”中取出一个,使用完毕之后再放回去。我们可以通过设定连接池最大连接数来防止系统无尽的与数据库连接。更为重要的是我们可以通过连接池的管理机制监视数据库的连接的数量﹑使用情况,为系统开发﹑测试及性能调整提供依据。

 

为什么使用连接池

连接,是我们的编程语言与数据库交互的一种方式。我们经常会听到这么一句话“数据库连接很昂贵“。

有人接受这种说法,却不知道它的真正含义。因此,下面通过实例解释它究竟是什么。

 

下面是 Mysql 数据库创建连接的的一段代码:

 

 

[java] view plaincopyprint?

 
 

 

  1. String connUrl ="jdbc:mysql://your.database.domain/yourDBname";  

  2. Class.forName("com.mysql.jdbc.Driver");  

  3. Connection con =DriverManager.getConnection (connUrl);  


当我们创建了一个 Connection 对象,它在内部都执行了什么:

 

 

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 万次连接,极大的浪费数据库的资源,并且极易造成数据库服务器内存溢出、拓机。

 

采用连接池技术后的过程如下:

 

数据库连接是一种关键的有限的昂贵的资源,这一点在多用户的网页应用程序中体现的尤为突出。对数据库连接的管理能显著影响到整个应用程序的伸缩性和健壮性,影响到程序的性能指标。数据库连接池负责分配,管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个。

 

需要注意的问题

1、并发问题

为了使连接管理服务具有最大的通用性,必须考虑多线程环境,即并发问题。这个问题相对比较好解决,因为各个语言自身提供了对并发管理的支持像 java,c# 等等,使用 synchronized(java)、lock(C#) 关键字即可确保线程是同步的。

2、事务处理

我们知道,事务具有原子性,此时要求对数据库的操作符合“ALL-OR-NOTHING”原则,即对于一组 SQL 语句要么全做,要么全不做。

我们知道当2个线程公用一个连接 Connection 对象,而且各自都有自己的事务要处理时候,对于连接池是一个很头疼的问题,因为即使 Connection 类提供了相应的事务支持,可是我们仍然不能确定那个数据库操作是对应那个事务的,这是由于我们有2个线程都在进行事务操作而引起的。为此我们可以使用每一个事务独占一个连接来实现,虽然这种方法有点浪费连接池资源但是可以大大降低事务管理的复杂性。

3、连接池的分配与释放

连接池的分配与释放,对系统的性能有很大的影响。合理的分配与释放,可以提高连接的复用度,从而降低建立新连接的开销,同时还可以加快用户的访问速度。

对于连接的管理可使用一个 List。即把已经创建的连接都放入 List 中去统一管理。每当用户请求一个连接时,系统检查这个 List 中有没有可以分配的连接。如果有就把那个最合适的连接分配给他(如何能找到最合适的连接文章将在关键议题中指出);如果没有就抛出一个异常给用户,List 中连接是否可以被分配由一个线程来专门管理。

4、连接池的配置与维护

连接池中到底应该放置多少连接,才能使系统的性能最佳?系统可采取设置最小连接数(minConnection)和最大连接数(maxConnection)等参数来控制连接池中的连接。比方说,最小连接数是系统启动时连接池所创建的连接数。如果创建过多,则系统启动就慢,但创建后系统的响应速度会很快;如果创建过少,则系统启动的很快,响应起来却慢。这样,可以在开发时,设置较小的最小连接数,开发起来会快,而在系统实际使用时设置较大的,因为这样对访问客户来说速度会快些。最大连接数是连接池中允许连接的最大数目,具体设置多少,要看系统的访问量,可通过软件需求上得到。

如何确保连接池中的最小连接数呢?有动态和静态两种策略。动态即每隔一定时间就对连接池进行检测,如果发现连接数量小于最小连接数,则补充相应数量的新连接,以保证连接池的正常运转。静态是发现空闲连接不够时再去检查。

 

Tomcat 连接池

Tomcat 默认使用的是 DBCP 数据库连接池,其实从本质上讲,Tomcat 是利用 Apache Commons DBCP 来实现的,只不过把特定的功能集成到了 tomcat-dbcp.jar 包中。

使用法法如下:

步骤 1:

在 Tomcat 中 Context.xml 中添加

 

[html] view plaincopyprint?

 
 

 

  1. <!--  path 表示站点的访问方式 -->  

  2. <!--    例:http://localhost:8080/test 配置为 /test -->  

  3. <!-- docBase="fileLocation" 应用存储的实际路径,没有的话则从 webapps 目录找 -->  

  4. <!-- Context 标签内的这些属性都可以省略不写,使用默认的设置 -->  

  5. <Context path="/TomcatDbPools" docBase="TomcatDbPools" debug="0" reloadable="true">  

  6.             <!--     使用 DBCP 配置的数据源        -->  

  7. <Resource   

  8. <!-- 指定资源池的 Resource 的 JNDI 的名字,就是给连接池起的名字 -->  

  9.        name="jdbc/mysql_connect"    

  10. <!-- 管理权限,指定管理 Resource 的 Manager,可以是 Container 或 Application -->  

  11.        auth="Container"            

  12. <!-- 指出 Resource 所属的类名,是什么类型的数据源 -->  

  13.        type="javax.sql.DataSource"    

  14. <!-- 数据库驱动类 -->  

  15.        driverClassName="com.mysql.jdbc.Driver"    

  16. <!-- 数据库连接 url-->  

  17.        url=" jdbc:mysql://localhost:3306/test"    

  18. <!-- 数据库用户名 -->  

  19.        username="admin"    

  20. <!-- 数据库密码 -->  

  21.        password="123456"    

  22. <!-- 连接池最大激活的连接数,设为 0 表示无限制 -->  

  23.        maxActive="100"    

  24. <!-- 连接池中最多可空闲的连接数 -->  

  25.        maxIdle="30"    

  26. <!-- 为连接最大的等待时间,单位毫秒,如果超过此时间将接到异常。设为 -1 表示无限制 -->  

  27.        maxWait="10000" />   

  28. </context>  

注:还可以用 minIdle 配置连接池中最少空闲 maxIdle 个连接,用 initialSize 配置初始化连接数目。可同时配置多个数据源。

 

 

 

如果在 Tomcat 的 server.xml 文件中配置数据源,有两种方法都可以实现:

方法 1:将上面的配置内容直接添加在 <Host> 节点下。

方法 2:在 <GlobalNamingResources> 节点下添加:

 

[html] view plaincopyprint?

 
 

 

  1. <GlobalNamingResources>  

  2. <!-- 这里的 factory 指的是该 Resource 配置使用的是哪个数据源配置类,这里使用的是 tomcat 自带的标准数据源 Resource 配置类,-->  

  3. <!-- 这个类也可以自己写,实现 javax.naming.spi.ObjectFactory 接口即可。 -->  

  4. <!-- 某些地方使用的 commons-dbcp.jar 中的 org.apache.commons.dbcp.BasicDataSourceFactory,-->  

  5. <!-- 如果使用这个就需把 commons-dbcp.jar 及其依赖的 jar 包,都放在 tomcat 的 lib 下,光放在工程的 WEB-INF/lib 下是不够的。  -->  

  6.     <Resource   

  7.     name="mysql_connect"                  

  8.     factory="org.apache.tomcat.dbcp.dbcp.BasicDataSourceFactory"  

  9.     maxActive="100"   

  10.     maxIdle="30"   

  11.     maxWait="10000"   

  12.     name="jdbc/TomcatDbPool1"   

  13.     password="123456"   

  14.     type="javax.sql.DataSource"   

  15.     url="jdbc:mysql://localhost:3306/test"   

  16.     username="root"/>               

  17. </GlobalNamingResources>  


然后在 context.xml 文件中的 <Context></Context> 节点中加入如下内容:

 

 

[html] view plaincopyprint?

 
 

 

  1. <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 资源。    

 

[html] view plaincopyprint?

 
 

 

  1. <resource-ref>  

  2.       <!-- 对该资源的描述语言 -->  

  3.    <description> dbcpconnect</description>  

  4.       <!-- 引用的资源名,必须与 Context.xml 中的名字一致 -->  

  5.    <res-ref-name> jdbc/mysql_connect </res-ref-name>  

  6.       <!-- 资源类型 -->  

  7.   <res-type>javax.sql.DataSource</res-type>  

  8.       <!-- 管理权限 -->  

  9.    <res-auth>Container</res-auth>  

  10. </resource-ref>  

 

 

步骤 3:

在 Web 应用中使用数据源

 

[java] view plaincopyprint?

 
 

 

  1. // 获得对数据源的引用:  

  2. Context ctx =new InitialContext();  

  3. //java:comp/env/ 是 java 中 JNDI 固定写法。  

  4. DataSource ds =(DataSource) ctx.lookup("java:comp/env/jdbc/mysql_connect ");  

  5. // 获得数据库连接对象:  

  6. Connection conn= ds.getConnection();  

  7. // 返回数据库连接到连接池:  

  8. conn.close();  

 

 

DBCP 连接池

DBCP 是 Apache 软件基金组织下的开源连接池实现,要使用 DBCP 数据源,需要应用程序应在系统中增加如下两个 jar 文件:

Commons-dbcp.jar:连接池的实现

Commons-pool.jar:连接池实现的依赖库

Tomcat 的连接池正是采用该连接池来实现的。该数据库连接池既可以与应用服务器整合使用,也可由应用程序独立使用。

 

步骤 1:

在类目录下加入 dbcp 的配置文件:dbcp.properties

 

[plain] view plaincopyprint?在CODE上查看代码片派生到我的代码片

 
 

 

  1. #数据库驱动  

  2. driverClassName=com.mysql.jdbc.Driver  

  3. #数据库连接地址  

  4. url=jdbc:mysql://localhost/test  

  5. #用户名  

  6. username=root  

  7. #密码  

  8. password=123456  

  9. #连接池的最大数据库连接数。设为 0 表示无限制  

  10. maxActive=30  

  11. #最大空闲数,数据库连接的最大空闲时间。超过空闲时间,数据库连  

  12. #接将被标记为不可用,然后被释放。设为 0 表示无限制  

  13. maxIdle=10  

  14. #最大建立连接等待时间。如果超过此时间将接到异常。设为 -1 表示无限制  

  15. maxWait=1000  

  16. #超过 removeAbandonedTimeout 时间后,是否进行没用连接(废弃)的回收(默认为 false,调整为 true)  

  17. removeAbandoned=true  

  18. #超过时间限制,回收没有用 (废弃) 的连接(默认为 300 秒)  

  19. removeAbandonedTimeout=180  

 

 

步骤 2:

在获取数据库连接的工具类 (如 jdbcUtils) 的静态代码块中创建池:

 

[java] view plaincopyprint?

 
 

 

  1. import java.io.InputStream;  

  2. import java.sql.Connection;  

  3. import java.sql.ResultSet;  

  4. import java.sql.SQLException;  

  5. import java.sql.Statement;  

  6. import java.util.Properties;  

  7. import javax.sql.DataSource;  

  8. import org.apache.commons.dbcp.BasicDataSourceFactory;  

  9.    

  10.       /** 

  11.      * 在 java 中,编写数据库连接池需实现 java.sql.DataSource 接口,每一种数据库连接池都是 DataSource 接口的实现 

  12.      * DBCP 连接池就是 java.sql.DataSource 接口的一个具体实现 

  13.      */  

  14. public classJdbcUtils_DBCP {  

  15.     

  16.     private static DataSource ds = null;  

  17.     // 在静态代码块中创建数据库连接池  

  18.     static{  

  19.         try{  

  20.             // 加载 dbcp.properties 配置文件  

  21.             InputStream in =JdbcUtils_DBCP.class.getClassLoader().getResourceAsStream("dbcp.properties");  

  22.             Properties prop = new Properties();  

  23.             prop.load(in);  

  24.             // 创建数据源  

  25.             ds =BasicDataSourceFactory.createDataSource(prop);  

  26.         }catch (Exception e) {  

  27.             throw newExceptionInInitializerError(e);  

  28.         }  

  29.     }  

  30.      

  31.     // 从数据源中获取数据库连接  

  32.     public static Connection getConnection()throws SQLException{  

  33.         // 从数据源中获取数据库连接  

  34.         return ds.getConnection();  

  35.     }  

  36.      

  37.     // 释放连接  

  38.     public static void release(Connection conn){  

  39.           if(conn!=null){  

  40.             try{  

  41.                 // 将 Connection 连接对象还给数据库连接池  

  42.                 conn.close();  

  43.             }catch (Exception e) {  

  44.                 e.printStackTrace();  

  45.             }  

  46.       }  

  47.  }  

  48. }  


 

步骤 3:

在应用中获取连接

      

[java] view plaincopyprint?

 
 

 

  1. Connection conn = null;  

  2. PreparedStatement st = null;  

  3. ResultSet rs = null;  

  4. try{  

  5.     // 获取数据库连接  

  6.     conn =JdbcUtils_DBCP.getConnection();  

  7.     ……  

  8. }catch (Exception e) {  

  9.     e.printStackTrace();  

  10. }finally{  

  11.     // 释放资源  

  12.     JdbcUtils_DBCP.release(conn);  

  13. }  

 

 

C3P0 连接池

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?

 
 

 

  1. <c3p0-config>  

  2.       

  3.    <!--  C3P0 的缺省 (默认) 配置,-->  

  4.    <!-- 如果在代码中“ComboPooledDataSourceds = new ComboPooledDataSource();”这样写就表示使用的是 C3P0 的缺省 (默认) 配置信息来创建数据源 -->  

  5.      

  6.    <default-config>  

  7.         <property name="driverClass">com.mysql.jdbc.Driver</property>  

  8.         <property name="jdbcUrl">jdbc:mysql://localhost:3306/anysearch</property>  

  9.         <property name="user">root</property>  

  10.         <property name="password">123456</property>  

  11.              <!-- 当连接池中的连接耗尽的时候 c3p0 一次同时获取的连接数。Default:3 -->  

  12.         <property name="acquireIncrement">5</property>  

  13.              <!-- 初始化的连接数,取值应在 minPoolSize 与 maxPoolSize 之间。Default: 3-->  

  14.         <property name="initialPoolSize">10</property>  

  15.              <!-- 连接池中保留的最小连接数 -->  

  16.         <property name="minPoolSize">5</property>  

  17.              <!-- 连接池中保留的最大连接数。Default:15 -->  

  18.         <property name="maxPoolSize">20</property>  

  19.              <!-- 定义在从数据库获取新连接失败后重复尝试的次数。Default: 30 -->  

  20.         <property name="acquireRetryAttempts">30</property>  

  21.              <!-- 两次连接中间隔时间,单位毫秒。Default: 1000 -->  

  22.         <property name="acquireRetryDelay">1000</property>  

  23.              <!-- 连接关闭时默认将所有未提交的操作回滚。Default: false -->  

  24.          <property name="autoCommitOnClose">false</property>  

  25.     </default-config>  

  26.       

  27.     <!-- C3P0 的命名配置,-->  

  28.     <!-- 如果在代码中“ComboPooledDataSourceds = new ComboPooledDataSource("MySQL");”这样写就表示使用的是 name 是 MySQL 的配置信息来创建数据源 -->  

  29.       

  30.     <named-config name="MySQL">  

  31.         <property name="driverClass">com.mysql.jdbc.Driver</property>  

  32.         <property name="jdbcUrl">jdbc:mysql://localhost:3306/test2</property>  

  33.         <property name="user">root</property>  

  34.         <property name="password">123456</property>  

  35.         <property name="acquireIncrement">5</property>  

  36.         <property name="initialPoolSize">10</property>  

  37.         <property name="minPoolSize">5</property>  

  38.         <property name="maxPoolSize">20</property>  

  39.     </named-config>  

  40.    

  41. </c3p0-config>  

 

还有更多可设置的参数,具体可查阅相关资料。

 

步骤 2:

在获取数据库连接的工具类 (如 jdbcUtils) 的静态代码块中创建池

 

 

[java] view plaincopyprint?

 
 

 

  1. import java.sql.Connection;  

  2. import java.sql.ResultSet;  

  3. import java.sql.SQLException;  

  4. import java.sql.Statement;  

  5. import com.mchange.v2.c3p0.ComboPooledDataSource;  

  6.    

  7. public class JdbcUtils_C3P0 {  

  8.      

  9.     private static ComboPooledDataSource ds =null;  

  10.     // 在静态代码块中创建数据库连接池  

  11.     static{  

  12.         try{  

  13.             // 通过代码创建 C3P0 数据库连接池  

  14.             /*ds = new ComboPooledDataSource(); 

  15.            ds.setDriverClass("com.mysql.jdbc.Driver"); 

  16.            ds.setJdbcUrl("jdbc:mysql://localhost:3306/test"); 

  17.             ds.setUser("root"); 

  18.             ds.setPassword("123456"); 

  19.             ds.setInitialPoolSize(10); 

  20.             ds.setMinPoolSize(5); 

  21.             ds.setMaxPoolSize(20);*/  

  22.              

  23.             // 通过读取 C3P0 的 xml 配置文件创建数据源,C3P0 的 xml 配置文件 c3p0-config.xml 必须放在 src 目录下  

  24.             //ds = newComboPooledDataSource();// 使用 C3P0 的默认配置来创建数据源  

  25.             ds = newComboPooledDataSource("MySQL");// 使用 C3P0 的命名配置来创建数据源  

  26.              

  27.         }catch (Exception e) {  

  28.             throw newExceptionInInitializerError(e);  

  29.         }  

  30.     }  

  31.      

  32.    // 从数据源中获取数据库连接  

  33.     public static Connection getConnection()throws SQLException{  

  34.         // 从数据源中获取数据库连接  

  35.         return ds.getConnection();  

  36.     }  

  37.      

  38.     // 释放链接  

  39.     public static void release(Connection conn){  

  40.         

  41.         if(conn!=null){  

  42.             try{  

  43.                 // 将 Connection 连接对象还给数据库连接池  

  44.                 conn.close();  

  45.             }catch (Exception e) {  

  46.                 e.printStackTrace();  

  47.             }  

  48.         }  

  49.     }  

  50. }  


 

步骤 3:

在应用中获取连接

    

[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片

 
 

 

  1. Connection conn = null;  

  2. PreparedStatement st = null;  

  3. ResultSet rs = null;  

  4. try{  

  5.     // 获取数据库连接  

  6.     conn = JdbcUtils_C3P0.getConnection();  

  7.     ……  

  8. }catch (Exception e) {  

  9.     e.printStackTrace();  

  10. }finally{  

  11.     // 释放资源  

  12.     JdbcUtils_C3P0release(conn);  

  13. }  

 

 

其他连接池

此外,还有其他的连接池可供选择,比如使用比较广泛的 Proxool。Proxool 是一种 Java 数据库连接池技术。是 sourceforge 下的一个开源项目,这个项目提供一个健壮、易用的连接池,最为关键的是这个连接池提供监控的功能,方便易用,便于发现连接泄漏的情况。

proxool 和 c3p0 能够更好的支持高并发,但是在稳定性方面略逊于 dpcp。

可根据项目的实际需要来选择连接池。