DBCP老矣,但能饭


声明:本文转载自https://my.oschina.net/wangxindong/blog/1549852,转载目的在于传递更多信息,仅供学习交流之用。如有侵权行为,请联系我,我会及时删除。

数据库连接池在J2EE领域是一个不可缺失的组件,DBCP作为一个老牌的数据库连接池一直在企业系统中默默的奉献着自己。比如我们的大部分系统还都是使用DBCP,那么我们很有必要对DBCP有一个熟悉的认识。鉴于这样的需求,我根据相关资料(文后都有列出)结合自己的认知重新做了一个梳理,一来自己可以在以后的工作中回过头来温习,二来也希望能够帮助其他同学对DBCP以及涉及到的相关概念和知识比如超时机制,连接池原理等有一个复习。我们都知道操作一个数据库的流程,创建数据源,获得链接,构造statement,执行请求,接下来我们逐步梳理总结。

一、相关概念复习

在谈DBCP之前我们先来复习一下几个相关的概念:

1.1、JNDI

Java Naming and Directory Interface (JNDI)JNDI API被用于执行名字和目录服务。它提供了一致的模型来存取和操作企业级的资源。JNDI的api位于javax.naming包中,它的作用是,它可以把对象放到一个容器中(JNDI容器),JAVA对象在这个容器中都有一个名称,程序则可以通过这个名称来获取对象,例如下面这样:

// Construct BasicDataSource   BasicDataSource bds = new BasicDataSource();   bds.setDriverClassName("org.apache.commons.dbcp2.TesterDriver");   bds.setUrl("jdbc:apache:commons:testdriver");   bds.setUsername("username");   bds.setPassword("password");    ic.rebind("jdbc/basic", bds);       // Use   InitialContext ic2 = new InitialContext();   DataSource ds = (DataSource) ic2.lookup("jdbc/basic");   assertNotNull(ds);   Connection conn = ds.getConnection();   assertNotNull(conn);   conn.close(); 

1.2、JDBC

JDBC Type 4.png

JDBC Type 4.png

 

JAVA数据库连接(Java Database Connectivity,简称JDBC),是java语言中用来规范话我们的客户端应用程序,比如我们的web应用程序等如何访问关系型数据库的应用程序接口,提供了诸如查询和更新数据库中数据的方法。
JDBC驱动程序一共有四种类型:

  • 类型1-JDBC-ODBC桥
  • 类型2-本地API驱动
  • 类型3-网络协议驱动
  • 类型4-本地协议驱动
    我们常用的是类型4-本地协议驱动,这种类型的驱动使用socket链接,直接在客户端和数据库之间进行通信。
    优点是1-访问速度快 2-最直接,最纯粹的JAVA实现
    缺点是1-需要每个数据库厂商提供自己的JDBC驱动。2-需要针对不同的数据库使用不同的驱动程序。
    JDBC的API在jdk的java.sql包中,扩展的内容在javax.sql包中。主要包括(斜体绿色代表接口,需驱动程序提供者来具体实现):
  • DriverManager:负责加载各种不同驱动程序(Driver),并根据不同的请求,向调用者返回相应的数据库连接(Connection)。
  • Driver:驱动程序,会将自身加载到DriverManager中去,并处理相应的请求并返回相应的数据库连接(Connection)。
  • Connection:数据库连接,负责进行与数据库间的通讯,SQL执行以及事务处理都是在某个特定Connection环境中进行的。可以产生用以执行SQL的Statement。
  • Statement:用以执行SQL查询和更新(针对静态SQL语句和单次执行)。
  • PreparedStatement:用以执行包含动态参数的SQL查询和更新(在服务器端编译,允许重复执行以提高效率)。
  • CallableStatement:用以调用数据库中的存储过程。
  • SQLException:代表在数据库连接的创建和关闭和SQL语句的执行过程中发生了例外情况(即错误)。

1.3、DBCP数据库连接池和JDBC之间的关系

数据库连接池负责创建(通过JDBC API)、管理、销毁数据库的连接。应用程序可以从数据库连接池中重复使用一个现有的连接,而不是重新创建一个。连接池,common-pool中的GenericObjectPool它负责缓存和管理连接;连接,这是是指PoolableConnection;连接池和连接一对多的关系。池化技术是通过commons-pool来实现的,每个连接是一个对象,换言之,是对象池的使用与管理。DBCP连接池是基于commons-pool这种对象池来实现的。

二、commons-pool的理解

Apache commons-pool是一宗对象池技术,我们使用的很多涉及池的场景一般都是基于该组件来实现的,DBCP数据库连接池也是基于commons-pool来实现的,因此我们先来了解下这种对象池技术。下面这个图是对象池的对象生命周期流程图。

对象池.png

对象池.png

三、DBCP核心类图及序列图

3.1、BasicDataSource.java

BasicDataSource.gif

BasicDataSource.gif

3.2、ConnectionFactory.java

ConnectionFactory.gif

ConnectionFactory.gif

3.3、PoolingDataSource.java

PoolingDataSource.gif

PoolingDataSource.gif

3.4、PoolingConnection.java

PoolingConnection.gif

PoolingConnection.gif

3.5、Delegating.java

Delegating.gif

Delegating.gif

3.6、AbandonedObjectPool.java

AbandonedObjectPool.gif

AbandonedObjectPool.gif

3.7、创建数据源createDataSource

createDataSource序列图.gif

createDataSource序列图.gif

3.8、创建连接getConnection

getConnection序列图.gif

getConnection序列图.gif

3.9、创建statement prepareStatement

prepareStatement序列图.gif

prepareStatement序列图.gif

四、DBCP配置及使用

DBCP是Apache下的一个开源数据库连接池,我们在使用的时候需要两个JAR文件,分别是commons-dbcp.jar(连接池的实现)和commons-pool.jar(连接池实现的依赖库),不过我们在使用的时候只需要引入下面mvaen坐标,commons-pool是在commons-dbcp里面隐含引用了。注意一点,就是从1.x升级到2.x的时候,由于dbcp的包路径已经变了,需要升级者修改局部代码。

<dependency>     <groupId>commons-dbcp</groupId>     <artifactId>commons-dbcp</artifactId>     <version>2.2</version> </dependency> 

DBCP可以在应用程序中独立的使用,也可以与web应用服务器整合使用。

4.1、tomcat中使用

比如tomcat的连接池就是采用该连接池来实现的。如下:

<Context>      <Resource name="jdbc/datasource" auth="Container"  type="javax.sql.DataSource"              username="root" password="root"  driverClassName="com.mysql.jdbc.Driver"              url="jdbc:mysql://192.168.0.1:3306/test"  maxActive="8" maxIdle="4"  />  </Context> 

4.2、应用程序中独立使用

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"  destroy-method="close">          <!--如果没有配置 destroy-method="close" ,但是重启次数如果太频繁的话,将造成重启tomcat后旧的数据库连接池的连接不释放,连接堆满了,后续启动无法建立连接-->                  <!--数据库连接相关配置,用户名、密码、连接地址、驱动-->         <property name="driverClassName" value="com.mysql.jdbc.Driver"/>           <property name="url" value="jdbc:mysql://192.168.0.1:3306/test?useUnicode=true&characterEncoding=UTF-8"/>           <property name="username" value="root"/>           <property name="password" value="root"/>          <!--数据库连接属性 connectTimeout:建立连接的超时时间,socketTimeout:客户端和服务进行数据交互的时间,是指两者之间如果两个数据包之间的时间大于该时间则认为超时-->         <property name="connectionProperties"                   value="connectTimeout=2000;socketTimeout=15000"/>                  <!--以下属性也有的建议,值配置一样-->         <!--initialSize: 初始化连接-->         <property name="initialSize" value="30"/>           <!--maxActive: 最大连接数量-->             <property name="maxActive" value="150"/>           <!--minIdle: 最小空闲连接-->             <property name="minIdle" value="5"/>           <!--maxIdle: 最大空闲连接-->             <property name="maxIdle" value="20"/>                             <!--等待获取连接池连接的时间(在1.x版本是maxWait),不要太大-->         <property name="maxWaitMillis" value="500"/>         <!--连接池中的连接空闲多久,从池中删除,单位MS。小于0,则表示不启动-->            <property name="minEvictableIdleTimeMillis" value="" />         <!--检查失效连接的定时器执行间隔,单位MS,小于0,则表示不启动。注意:mysql数据库如果8小时内没有连接请求,则连接会自动断开,因此这个参数如果并发量不大的情况下还是要配上-->             <property name="timeBetweenEvictionRunsMillis" value="" />             </bean> 

更多的配置请参照https://commons.apache.org/proper/commons-dbcp/configuration.html

五、连接超时机制

对网络资源访问的时候,超时设置是必须的。没有超时的保护,一旦依赖资源发生故障或者网络故障,就会引起线程堆积,甚至发生雪崩。

5.1、超时层级

超时层次.png

超时层次.png

 

从这张图中我们也能够看出DBCP并不参与数据库超时的处理,它只负责管理连接。根据上图我们可以看到超时有一个依赖层级,上层超时依赖下层超时。依次为:事务超时->Statement超时->JDBC Driver socket超时->操作系统超时。

5.1.1、事务超时:

事务是应用层级的概念,我们知道事务是有一组SQL执行单元组成的。那么事务超时的时间阈值,实际是Statement超时N个需要执行的Statement数量。比如一个事务里面有3条Statement,每条Statement的执行时间是50MS,其它业务上的逻辑执行时间+框架执行时间未100ms,那么最终事务的超时时间为:350+100=250ms。

5.1.2、tatement超时:

用来限制sql语句的执行时间,通过setQueryTimeout(int timeout)来设置,不过现在大都是ibatis了,可以通过 SqlMapConfig.xml 中的 setting 属性defaultStatementTimeout 来设置全局的 statement 超时缺省值<settings defaultStatementTimeout="15"/>,还可以在每个sql.xml文件中,根据业务实际需要来设置<select timeout="10"/> <insert timeout="10"/> <update timeout="10"/>,这样就会覆盖掉全局的值,而采用具体的阈值。

5.1.3、Socket超时:

这是底层的一种超时,因为我们使用的JDBC驱动类型是TYPE4,它是基于socket来通信的。mysql的jdbc驱动中的connectTimeout 和 socketTimeout 的默认值是 0 ,这意味着不会发生超时。所以我们必须在dbcp配置中设置这两个值。<property name="connectionProperties" value="connectTimeout=2000;socketTimeout=15000"/>
connectTimeout为建立连接的超时时间,socketTimeout为JDBC客户端和数据库服务器之间数据交互的时间。注意这里配置的socketTimeout的值必须要大于Statement的超时时间值。否则Statement超时就没有意义,也不能生效。

5.1.4、操作系统的socket超时

linux操作系统也会设置socket超时,比如我们这边的服务器一般配置的是20分钟,因为公司的linux服务器的KeepAlive检查周期为20分钟。这样即使上面的socketTimeout值为0用不超时,也还是要收到linux服务器的超时限制,也就是由于网络原因引起的数据库网络连接问题也不会超过20分钟。

5.2、Mysql处理超时的机制及原理

MySQL 的 Statement 超时执行过程.png

MySQL 的 Statement 超时执行过程.png

 

上图是一mysql在执行一个命令的过程中的步骤以及发生超时现象后的处理机制
1、通过Connection的createStatement()方法去创建一个Statement,以便后续进行读写操作
2、执行第1步创建的Statement的executeQuery()方法
3、将查询请求命令发送到mysql数据库服务器
4、创建一个超时线程(从5.1版本以后在,创建每个连接的时候,会随之创建一个处理超时的线程timeout-execution)
5、把当前的statement对象注册到超时线程timeout-execution中
6、发生了超时(阈值是你在Statement执行前候配置的setQueryTimeout(int timeout),如果是mybatis则是在配置文件里面配置的值defaultStatementTimeout="15",单位s)
7、超时线程会重新创建一个新的Connection,这个Connection的属性配置都跟先前的一样
8、用新创建的这个Connection去发送取消查询请求

版权声明:本篇文章未经作者允许不得转载,谢谢。

参考资料
https://www.cubrid.org/blog/understanding-jdbc-internals-and-timeout-configuration
https://zh.wikipedia.org/wiki/Java%E6%95%B0%E6%8D%AE%E5%BA%93%E8%BF%9E%E6%8E%A5
http://shift-alt-ctrl.iteye.com/blog/1917782
https://commons.apache.org/proper/commons-pool/index.html
https://commons.apache.org/proper/commons-dbcp/index.html

本文发表于2017年10月12日 22:34
(c)注:本文转载自https://my.oschina.net/wangxindong/blog/1549852,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责。如有侵权行为,请联系我们,我们会及时删除.

阅读 1891 讨论 0 喜欢 0

抢先体验

扫码体验
趣味小程序
文字表情生成器

闪念胶囊

你要过得好哇,这样我才能恨你啊,你要是过得不好,我都不知道该恨你还是拥抱你啊。

直抵黄龙府,与诸君痛饮尔。

那时陪伴我的人啊,你们如今在何方。

不出意外的话,我们再也不会见了,祝你前程似锦。

这世界真好,吃野东西也要留出这条命来看看

快捷链接
网站地图
提交友链
Copyright © 2016 - 2021 Cion.
All Rights Reserved.
京ICP备2021004668号-1