diff --git a/README.md b/README.md
index e0fd9b7..3ba2d2d 100644
--- a/README.md
+++ b/README.md
@@ -1,58 +1,8 @@
-# 目录
-
-* [简介](#简介)
-* [使用例子](#使用例子)
- * [需求](#需求)
- * [工程环境](#工程环境)
- * [主要步骤](#主要步骤)
- * [创建项目](#创建项目)
- * [引入依赖](#引入依赖)
- * [编写`jdbc.prperties`](#编写jdbcprperties)
- * [获取连接池和获取连接](#获取连接池和获取连接)
- * [编写测试类](#编写测试类)
-* [配置文件详解](#配置文件详解)
- * [数据库连接参数](#数据库连接参数)
- * [连接池基本参数](#连接池基本参数)
- * [连接存活参数](#连接存活参数)
- * [连接检查参数](#连接检查参数)
- * [缓存语句](#缓存语句)
- * [事务相关参数](#事务相关参数)
- * [连接泄漏回收参数](#连接泄漏回收参数)
- * [其他](#其他)
-* [源码分析](#源码分析)
- * [创建数据源](#创建数据源)
- * [`BasicDataSource.getConnection()`](#basicdatasourcegetconnection)
- * [`BasicDataSource.createDataSource()`](#basicdatasourcecreatedatasource)
- * [获取连接对象](#获取连接对象)
- * [`PoolingDataSource.getConnection()`](#poolingdatasourcegetconnection)
- * [`GenericObjectPool.borrowObject()`](#genericobjectpoolborrowobject)
- * [`GenericObjectPool.create()`](#genericobjectpoolcreate)
- * [`PoolableConnectionFactory.makeObject()`](#poolableconnectionfactorymakeobject)
- * [空闲对象回收器`Evictor`](#空闲对象回收器evictor)
- * [`BasicDataSource.startPoolMaintenance()`](#basicdatasourcestartpoolmaintenance)
- * [`BaseGenericObjectPool.setTimeBetweenEvictionRunsMillis(long)`](#basegenericobjectpoolsettimebetweenevictionrunsmillislong)
- * [`BaseGenericObjectPool.startEvictor(long)`](#basegenericobjectpoolstartevictorlong)
- * [`EvictionTimer.schedule(Evictor, long, long)`](#evictiontimerscheduleevictor-long-long)
- * [`BaseGenericObjectPool.Evictor`](#basegenericobjectpoolevictor)
- * [`GenericObjectPool.evict()`](#genericobjectpoolevict)
-* [通过`JNDI`获取数据源对象](#通过jndi获取数据源对象)
- * [需求](#需求-1)
- * [引入依赖](#引入依赖-1)
- * [编写`context.xml`](#编写contextxml)
- * [编写`web.xml`](#编写webxml)
- * [编写`jsp`](#编写jsp)
- * [测试结果](#测试结果)
-* [使用`DBCP`测试两阶段提交](#使用dbcp测试两阶段提交)
- * [准备工作](#准备工作)
- * [`mysql`的`XA`事务使用](#mysql的xa事务使用)
- * [引入依赖](#引入依赖-2)
- * [获取`BasicManagedDataSource`](#获取basicmanageddatasource)
- * [编写两阶段提交的代码](#编写两阶段提交的代码)
# 简介
-`DBCP`用于创建和管理连接,利用“池”的方式复用连接减少资源开销,和其他连接池一样,也具有连接数控制、连接可靠性测试、连接泄露控制、缓存语句等功能。目前,`tomcat`自带的连接池就是`DBCP`,Spring开发组也推荐使用`DBCP`。
+`DBCP`用于创建和管理连接,利用“池”的方式复用连接减少资源开销,和其他连接池一样,也具有连接数控制、连接有效性检测、连接泄露控制、缓存语句等功能。目前,`tomcat`自带的连接池就是`DBCP`,Spring开发组也推荐使用`DBCP`,阿里的`druid`也是参照`DBCP`开发出来的。
`DBCP`除了我们熟知的使用方式外,还支持通过`JNDI`获取数据源,并支持获取`JTA`或`XA`事务中用于`2PC`(两阶段提交)的连接对象,本文也将以例子说明。
@@ -64,40 +14,43 @@
4. `DBCP`其他特性的使用方法,如`JNDI`和`JTA`支持。
-
# 使用例子
## 需求
+
使用`DBCP`连接池获取连接对象,对用户数据进行简单的增删改查。
## 工程环境
-`JDK`:1.8.0_201
-`maven`:3.6.1
+`JDK`:1.8.0_201
+
+`maven`:3.6.1
-`IDE`:eclipse 4.12
+`IDE`:eclipse 4.12
-`mysql-connector-java`:8.0.15
+`mysql-connector-java`:8.0.15
-`mysql`:5.7
+`mysql`:5.7.28
-`DBCP`:2.6.0
+`DBCP`:2.6.0
## 主要步骤
-1. 编写`jdbc.properties`,设置数据库连接参数和连接池基本参数等。
+1. 编写`dbcp.properties`,设置数据库连接参数和连接池基本参数等。
-2. 通过`BasicDataSourceFactory`加载`jdbc.properties`,并获得`BasicDataDource`对象。
+2. 通过`BasicDataSourceFactory`加载`dbcp.properties`,并获得`BasicDataDource`对象。
-3. 通过`BasicDataDource`对象获取`Connection`对象。
+3. 通过`BasicDataDource`对象获取`Connection`对象。
-4. 使用`Connection`对象对用户表进行增删改查。
+4. 使用`Connection`对象对用户表进行增删改查。
## 创建项目
-项目类型Maven Project,打包方式war(其实jar也可以,之所以使用war是为了测试`JNDI`)。
+
+项目类型Maven Project,打包方式war(其实jar也可以,之所以使用war是为了测试`JNDI`)。
## 引入依赖
+
```xml
@@ -126,46 +79,50 @@
```
-## 编写`jdbc.prperties`
-路径`resources`目录下,因为是入门例子,这里仅给出数据库连接参数和连接池基本参数,后面源码会对配置参数进行详细说明。另外,数据库`sql`脚本也在该目录下。
+## 编写`dbcp.prperties`
+
+路径`resources`目录下,因为是入门例子,这里仅给出数据库连接参数和连接池基本参数,后面源码会对配置参数进行详细说明。另外,数据库`sql`脚本也在该目录下。
```properties
-#数据库基本配置
+#连接基本属性
driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/github_demo?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&useSSL=true
username=root
password=root
-#-------------连接数据相关参数--------------------------------
-#初始化连接:连接池启动时创建的初始化连接数量
+#-------------连接池大小和连接超时参数--------------------------------
+#初始化连接数量:连接池启动时创建的初始化连接数量
#默认为0
initialSize=0
-#最大活动连接
-#连接池在同一时间能够分配的最大活动连接的数量, 如果设置为非正数则表示不限制
+
+#最大活动连接数量:连接池在同一时间能够分配的最大活动连接的数量, 如果设置为负数则表示不限制
#默认为8
-maxActive=8
-#最大空闲连接
-#连接池中容许保持空闲状态的最大连接数量,超过的空闲连接将被释放,如果设置为负数表示不限制
+maxTotal=8
+
+#最大空闲连接:连接池中容许保持空闲状态的最大连接数量,超过的空闲连接将被释放,如果设置为负数表示不限制
#默认为8
maxIdle=8
-#最小空闲连接
-#连接池中容许保持空闲状态的最小连接数量,低于这个数量将创建新的连接,如果设置为0则不创建
+
+#最小空闲连接:连接池中容许保持空闲状态的最小连接数量,低于这个数量将创建新的连接,如果设置为0则不创建
+#注意:timeBetweenEvictionRunsMillis为正数时,这个参数才能生效。
#默认为0
minIdle=0
+
#最大等待时间
-#当没有可用连接时,连接池等待连接被归还的最大时间(以毫秒计数),超过时间则抛出异常,如果设置为-1表示无限等待
-#默认无限
-maxWait=-1
+#当没有可用连接时,连接池等待连接被归还的最大时间(以毫秒计数),超过时间则抛出异常,如果设置为<=0表示无限等待
+#默认-1
+maxWaitMillis=-1
```
-## 获取连接池和获取连接
-项目中编写了`JDBCUtil`来初始化连接池、获取连接、管理事务和释放资源等,具体参见项目源码。
+## 获取连接池和获取连接
+
+项目中编写了`JDBCUtils`来初始化连接池、获取连接、管理事务和释放资源等,具体参见项目源码。
路径:`cn.zzs.dbcp`
```java
// 导入配置文件
Properties properties = new Properties();
- InputStream in = JDBCUtil.class.getClassLoader().getResourceAsStream("jdbc.properties");
+ InputStream in = JDBCUtil.class.getClassLoader().getResourceAsStream("dbcp.properties");
properties.load(in);
// 根据配置文件内容获得数据源对象
DataSource dataSource = BasicDataSourceFactory.createDataSource(properties);
@@ -174,6 +131,7 @@ maxWait=-1
```
## 编写测试类
+
这里以保存用户为例,路径test目录下的`cn.zzs.dbcp`。
```java
@@ -185,9 +143,9 @@ maxWait=-1
PreparedStatement statement = null;
try {
// 获得连接
- connection = JDBCUtil.getConnection();
+ connection = JDBCUtils.getConnection();
// 开启事务设置非自动提交
- JDBCUtil.startTrasaction();
+ JDBCUtils.startTrasaction();
// 获得Statement对象
statement = connection.prepareStatement(sql);
// 设置参数
@@ -199,13 +157,13 @@ maxWait=-1
// 执行
statement.executeUpdate();
// 提交事务
- JDBCUtil.commit();
+ JDBCUtils.commit();
} catch(Exception e) {
- JDBCUtil.rollback();
+ JDBCUtils.rollback();
log.error("保存用户失败", e);
} finally {
// 释放资源
- JDBCUtil.release(connection, statement, null);
+ JDBCUtils.release(connection, statement, null);
}
}
```
@@ -214,9 +172,10 @@ maxWait=-1
# 配置文件详解
-这部分内容从网上参照过来,同样的内容发的到处都是,暂时没找到出处。因为内容太过杂乱,而且最新版本更新了不少内容,所以我花了好大功夫才改好,后面找到出处再补上参考资料吧。
+这部分内容从网上参照过来,同样的内容发的到处都是,暂时没找到出处。因为内容太过杂乱,而且最新版本更新了不少内容,所以我花了好大功夫才改好,后面找到出处再补上参考资料吧。
+
+## 基本连接属性
-## 数据库连接参数
注意,这里在`url`后面拼接了多个参数用于避免乱码、时区报错问题。 补充下,如果不想加入时区的参数,可以在`mysql`命令窗口执行如下命令:`set global time_zone='+8:00'`。
@@ -227,11 +186,12 @@ username=root
password=root
```
-## 连接池基本参数
-这几个参数都比较常用,具体设置多少需根据项目调整。
+## 连接池大小参数
+
+这几个参数都比较常用,具体设置多少需根据项目调整。
```properties
-#-------------连接数据相关参数--------------------------------
+#-------------连接池大小和连接超时参数--------------------------------
#初始化连接数量:连接池启动时创建的初始化连接数量
#默认为0
initialSize=0
@@ -253,24 +213,39 @@ minIdle=0
#当没有可用连接时,连接池等待连接被归还的最大时间(以毫秒计数),超过时间则抛出异常,如果设置为<=0表示无限等待
#默认-1
maxWaitMillis=-1
+
+#连接池创建的连接的默认的数据库名,如果是使用DBCP的XA连接必须设置,不然注册不了多个资源管理器
+#defaultCatalog=github_demo
+
+#连接池创建的连接的默认的schema。如果是mysql,这个设置没什么用。
+#defaultSchema=github_demo
```
-## 连接存活参数
+
+## 缓存语句
+
+缓存语句在`mysql`下建议关闭。
+
```properties
-#资源池中资源最小空闲时间(单位为毫秒),达到此值后将被移除。
-#默认值1000*60*30 = 30分钟
-minEvictableIdleTimeMillis=1800000
+#-------------缓存语句--------------------------------
+#是否缓存preparedStatement,也就是PSCache。
+#PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭
+#默认为false
+poolPreparedStatements=false
-#资源池中资源最小空闲时间(单位为毫秒),达到此值后将被移除。但是会保证minIdle
-#默认值-1
-#softMinEvictableIdleTimeMillis=-1
+#缓存PreparedStatements的最大个数
+#默认为-1
+#注意:poolPreparedStatements为true时,这个参数才有效
+maxOpenPreparedStatements=-1
+
+#缓存read-only和auto-commit状态。设置为true的话,所有连接的状态都会是一样的。
+#默认是true
+cacheState=true
-#连接最大存活时间。非正数表示不限制
-#默认-1
-maxConnLifetimeMillis=-1
```
## 连接检查参数
-针对连接失效和连接泄露的问题,建议开启`testWhileIdle`,而不是开启`testOnReturn`或`testOnBorrow`,从(从性能考虑)。
+
+针对连接失效和连接泄露的问题,建议开启`testWhileIdle`,而不是开启`testOnReturn`或`testOnBorrow`(从性能考虑)。
```properties
#-------------连接检查情况--------------------------------
@@ -283,53 +258,49 @@ validationQuery=select 1 from dual
validationQueryTimeout=-1
#是否从池中取出连接前进行检验。
-#默认为true。不建议开启。
+#默认为true
testOnBorrow=true
#是否在归还到池中前进行检验
#默认为false
testOnReturn=false
-#是否开启空闲对象回收器。
+#是否开启空闲资源回收器。
#默认为false
testWhileIdle=false
-#空闲对象回收器的检测周期(单位为毫秒)。
-#默认-1。即空闲对象回收器不工作。
+#空闲资源的检测周期(单位为毫秒)。
+#默认-1。即空闲资源回收器不工作。
timeBetweenEvictionRunsMillis=-1
-#做空闲对象回收器时,每次的采样数。
+#做空闲资源回收器时,每次的采样数。
#默认3,单位毫秒。如果设置为-1,就是对所有连接做空闲监测。
numTestsPerEvictionRun=3
-#空闲对象回收器的回收策略
+#资源池中资源最小空闲时间(单位为毫秒),达到此值后将被移除。
+#默认值1000*60*30 = 30分钟
+minEvictableIdleTimeMillis=1800000
+
+#资源池中资源最小空闲时间(单位为毫秒),达到此值后将被移除。但是会保证minIdle
+#默认值-1
+#softMinEvictableIdleTimeMillis=-1
+
+#空闲资源回收策略
#默认org.apache.commons.pool2.impl.DefaultEvictionPolicy
#如果要自定义的话,需要实现EvictionPolicy重写evict方法
evictionPolicyClassName=org.apache.commons.pool2.impl.DefaultEvictionPolicy
-```
-## 缓存语句
-
-缓存语句在`mysql`下建议关闭。
-
-```properties
-#-------------缓存语句--------------------------------
-#是否缓存PreparedStatements
-#PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭
-#默认为false
-poolPreparedStatements=false
-
-#缓存PreparedStatements的最大个数
-#默认为-1
-#注意:poolPreparedStatements为true时,这个参数才有效
-maxOpenPreparedStatements=-1
-
-#缓存read-only和auto-commit状态。设置为true的话,所有连接的状态都会是一样的。
-#默认是true
-cacheState=true
+#连接最大存活时间。非正数表示不限制
+#默认-1
+maxConnLifetimeMillis=-1
+#当达到maxConnLifetimeMillis被关闭时,是否打印相关消息
+#默认true
+#注意:maxConnLifetimeMillis设置为正数时,这个参数才有效
+logExpiredConnections=true
```
+
## 事务相关参数
这里的参数主要和事务相关,一般默认就行。
@@ -357,11 +328,6 @@ autoCommitOnReturn=true
#默认true
rollbackOnReturn=true
-#连接池创建的连接的默认的数据库名,如果是使用DBCP的XA连接必须设置,不然注册不了多个资源管理器
-#defaultCatalog=github_demo
-
-#连接池创建的连接的默认的schema。如果是mysql,这个设置没什么用。
-#defaultSchema=github_demo
```
## 连接泄漏回收参数
@@ -375,9 +341,9 @@ rollbackOnReturn=true
#注意:这个机制在(getNumIdle() < 2) and (getNumActive() > (getMaxActive() - 3))时被触发
removeAbandonedOnBorrow=false
-#当未使用的时间超过removeAbandonedTimeout时,是否视该连接为泄露连接并删除
+#当未使用的时间超过removeAbandonedTimeout时,是否视该连接为泄露连接并删除(空闲evictor检测)
#默认为false
-#注意:当空闲对象回收器开启才生效
+#注意:当空闲资源回收器开启才生效
removeAbandonedOnMaintenance=false
#泄露的连接可以被删除的超时值, 单位秒
@@ -399,11 +365,7 @@ abandonedUsageTracking=false
这部分参数比较少用。
```properties
-#当达到maxConnLifetimeMillis被关闭时,是否打印相关消息
-#默认true
-#注意:maxConnLifetimeMillis设置为正数时,这个参数才有效
-logExpiredConnections=true
-
+#-------------其他--------------------------------
#是否使用快速失败机制
#默认为空,由驱动决定
fastFailValidation=false
@@ -442,15 +404,17 @@ accessToUnderlyingConnectionAllowed=false
```
-
# 源码分析
-通过使用例子可知,`DBCP`的`BasicDataSource`是我们获取连接对象的入口,至于`BasicDataSourceFactory`只是创建和初始化`BasicDataSource`实例,它的代码就不看了。这里直接从`BasicDataSource`的`getConnection()`方法开始分析。
+注意:考虑篇幅和可读性,以下代码经过删减,仅保留所需部分。
-注意:考虑篇幅和可读性,以下代码经过删减,仅保留所需部分。
+## 创建数据源和连接池
-## 创建数据源
-研究数据源创建之前,先来看下`DBCP`的几种数据源:
+研究之前,先来看下`BasicDataSource`的`UML`图:
+
+
+
+这里介绍下这几个类的作用:
类名|描述
-|-
@@ -458,15 +422,20 @@ accessToUnderlyingConnectionAllowed=false
`BasicManagedDataSource`|`BasicDataSource`的子类,用于创建支持`XA`事务或`JTA`事务的连接
`PoolingDataSource`|`BasicDataSource`中实际调用的数据源,可以说`BasicDataSource`只是封装了`PoolingDataSource`
`ManagedDataSource`|`PoolingDataSource`的子类,用于支持`XA`事务或`JTA`事务的连接。是`BasicManagedDataSource`中实际调用的数据源,可以说`BasicManagedDataSource`只是封装了`ManagedDataSource`
-`InstanceKeyDataSource`|用于支持`JDNI`环境的数据源
-`PerUserPoolDataSource`|`InstanceKeyDataSource`的子类,针对每个用户会单独分配一个连接池,每个连接池可以设置不同属性。例如以下需求,相比user,`admin`可以创建更多地连接以保证
-`SharedPoolDataSource`|`InstanceKeyDataSource`的子类,不同用户共享一个连接池
-本文的源码分析仅会涉及到`BasicDataSource`(包含它封装的`PoolingDataSource`),其他的数据源暂时不扩展。
+另外,为了支持`JNDI`,`DBCP`也提供了相应的类。
-### `BasicDataSource.getConnection()`
+| 类名 | 描述 |
+| ----------------------- | ------------------------------------------------------------ |
+| `InstanceKeyDataSource` | 用于支持`JDNI`环境的数据源 |
+| `PerUserPoolDataSource` | `InstanceKeyDataSource`的子类,针对每个用户会单独分配一个连接池,每个连接池可以设置不同属性。例如以下需求,相比user,`admin`可以创建更多地连接以保证 |
+| `SharedPoolDataSource` | `InstanceKeyDataSource`的子类,不同用户共享一个连接池 |
+
+本文的源码分析仅会涉及到`BasicDataSource`(包含它封装的`PoolingDataSource`),其他的数据源暂时不扩展。
-`BasicDataSource`是在第一次被调用获取获取连接时才创建`PoolingDataSource`对象。
+### BasicDataSource.getConnection()
+
+`BasicDataSourceFactory`只是简单地`new`了一个`BasicDataSource`对象并初始化配置参数,此时真正的数据源(`PoolingDataSource`)以及连接池(`GenericObjectPool`)并没有创建,而创建的时机为我们第一次调用`getConnection()`的时候。因此,本文直接从`BasicDataSource`的`getConnection()`方法开始分析。
```java
public Connection getConnection() throws SQLException {
@@ -474,104 +443,79 @@ accessToUnderlyingConnectionAllowed=false
}
```
-### `BasicDataSource.createDataSource()`
-接下来的方法又会涉及到四个类,如下:
+### BasicDataSource.createDataSource()
-| 类名 | 描述 |
-| --------------------------- | ------------------------------------------------------------ |
-| `ConnectionFactory` | 用于生成原生的Connection对象 |
-| `PoolableConnectionFactory` | 用于生成包装过的Connection对象,持有`ConnectionFactory`对象的引用 |
-| `GenericObjectPool` | 数据库连接池,用于管理连接。持有`PoolableConnectionFactory`对象的引用 |
-| `PoolingDataSource` | 数据源,持有`GenericObjectPool`的引用。我们调用`BasicDataSource`获取连接对象,实际上调用的是它的`getConnection()`方法 |
+这个方法会创建数据源和连接池,整个过程可以概括为以下几步:
+
+1. 注册`MBean`,用于支持`JMX`;
+2. 创建连接池对象`GenericObjectPool`;
+3. 创建数据源对象`PoolingDataSource`;
+4. 初始化连接数;
+5. 开启空闲资源回收线程(如果设置`timeBetweenEvictionRunsMillis`为正数)。
```java
- // 数据源
- private volatile DataSource dataSource;
- // 连接池
- private volatile GenericObjectPool connectionPool;
-
- protected DataSource createDataSource() throws SQLException {
- if (closed) {
- throw new SQLException("Data source is closed");
- }
- if (dataSource != null) {
- return dataSource;
- }
+ protected DataSource createDataSource() throws SQLException {
+ if(closed) {
+ throw new SQLException("Data source is closed");
+ }
+ if(dataSource != null) {
+ return dataSource;
+ }
- synchronized (this) {
- if (dataSource != null) {
- return dataSource;
- }
- // 注册MBean,用于支持JMX,这方面的内容不在这里扩展
- jmxRegister();
+ synchronized(this) {
+ if(dataSource != null) {
+ return dataSource;
+ }
+ // 注册MBean,用于支持JMX,这方面的内容不在这里扩展
+ jmxRegister();
+
+ // 创建原生Connection工厂:本质就是持有数据库驱动对象和几个连接参数
+ final ConnectionFactory driverConnectionFactory = createConnectionFactory();
+
+ // 将driverConnectionFactory包装成池化Connection工厂
+ PoolableConnectionFactory poolableConnectionFactory = createPoolableConnectionFactory(driverConnectionFactory);
+ // 设置PreparedStatements缓存(其实在这里可以发现,上面创建池化工厂时就设置了缓存,这里没必要再设置一遍)
+ poolableConnectionFactory.setPoolStatements(poolPreparedStatements);
+ poolableConnectionFactory.setMaxOpenPreparedStatements(maxOpenPreparedStatements);
+
+ // 创建数据库连接池对象GenericObjectPool,用于管理连接
+ // BasicDataSource将持有GenericObjectPool对象
+ createConnectionPool(poolableConnectionFactory);
+
+ // 创建PoolingDataSource对象
+ // 该对象持有GenericObjectPool对象的引用
+ DataSource newDataSource = createDataSourceInstance();
+ newDataSource.setLogWriter(logWriter);
+
+ // 根据我们设置的initialSize创建初始连接
+ for(int i = 0; i < initialSize; i++) {
+ connectionPool.addObject();
+ }
+
+ // 开启连接池的evictor线程
+ startPoolMaintenance();
+ // 最后BasicDataSource将持有上面创建的PoolingDataSource对象
+ dataSource = newDataSource;
+ return dataSource;
+ }
+ }
+```
+以上方法涉及到几个类,这里再补充下`UML`图。
- // 创建原生Connection工厂:本质就是持有数据库驱动对象和几个连接参数
- final ConnectionFactory driverConnectionFactory = createConnectionFactory();
+
- // 将driverConnectionFactory包装成池化Connection工厂
- boolean success = false;
- PoolableConnectionFactory poolableConnectionFactory;
- try {
- poolableConnectionFactory = createPoolableConnectionFactory(driverConnectionFactory);
- // 设置PreparedStatements缓存(其实在这里可以发现,上面创建池化工厂时就设置了缓存,这里没必要再设置一遍)
- poolableConnectionFactory.setPoolStatements(poolPreparedStatements);
- poolableConnectionFactory.setMaxOpenPreparedStatements(maxOpenPreparedStatements);
- success = true;
- } catch (final SQLException se) {
- throw se;
- } catch (final RuntimeException rte) {
- throw rte;
- } catch (final Exception ex) {
- throw new SQLException("Error creating connection factory", ex);
- }
- if (success) {
- // 创建数据库连接池对象GenericObjectPool,用于管理连接
- // BasicDataSource将持有GenericObjectPool对象
- createConnectionPool(poolableConnectionFactory);
- }
-
- // 创建PoolingDataSource对象
- //该对象持有GenericObjectPool对象的引用
- DataSource newDataSource;
- success = false;
- try {
- newDataSource = createDataSourceInstance();
- newDataSource.setLogWriter(logWriter);
- success = true;
- } catch (final SQLException se) {
- throw se;
- } catch (final RuntimeException rte) {
- throw rte;
- } catch (final Exception ex) {
- throw new SQLException("Error creating datasource", ex);
- } finally {
- if (!success) {
- closeConnectionPool();
- }
- }
+| 类名 | 描述 |
+| --------------------------- | ------------------------------------------------------------ |
+| `DriverConnectionFactory` | 用于生成原生的Connection对象 |
+| `PoolableConnectionFactory` | 用于生成池化的Connection对象,持有`ConnectionFactory`对象的引用 |
+| `GenericObjectPool` | 数据库连接池,用于管理连接。持有`PoolableConnectionFactory`对象的引用 |
- // 根据我们设置的initialSize创建初始连接
- try {
- for (int i = 0; i < initialSize; i++) {
- connectionPool.addObject();
- }
- } catch (final Exception e) {
- closeConnectionPool();
- throw new SQLException("Error preloading the connection pool", e);
- }
+## 获取连接对象
- // 开启连接池的evictor线程
- startPoolMaintenance();
- // 最后BasicDataSource将持有上面创建的PoolingDataSource对象
- dataSource = newDataSource;
- return dataSource;
- }
- }
-```
+上面已经大致分析了数据源和连接池对象的获取过程,接下来研究下连接对象的获取。在此之前先了解下`DBCP`中几个`Connection`实现类。
-## 获取连接对象
-上面已经大致分析了数据源对象的获取过程,接下来研究下连接对象的获取。在此之前先了解下`DBCP`中几个`Connection`实现类。
+
类名|描述
-|-
@@ -582,9 +526,15 @@ accessToUnderlyingConnectionAllowed=false
`ManagedConnection`|用于包装原生的`Connection`,支持`JTA`和`XA`事务
`PoolGuardConnectionWrapper`|用于包装`PoolableConnection`,当`accessToUnderlyingConnectionAllowed`才能获取底层连接对象。我们获取到的就是这个对象
+另外,这里先概括下获得连接的整个过程:
+1. 如果设置了`removeAbandonedOnBorrow`,达到条件会进行检测;
+2. 从连接池中获取连接,如果没有就通过工厂创建(通过`DriverConnectionFactory`创建原生对象,再通过`PoolableConnectionFactory`包装为池化对象);
+3. 通过工厂重新初始化连接对象;
+4. 如果设置了`testOnBorrow`或者`testOnCreate`,会通过工厂校验连接有效性;
+5. 使用`PoolGuardConnectionWrapper`包装连接对象,并返回给客户端
-### `PoolingDataSource.getConnection()`
+### PoolingDataSource.getConnection()
前面已经说过,`BasicDataSource`本质上是调用`PoolingDataSource`的方法来获取连接,所以这里从`PoolingDataSource.getConnection()`开始研究。
@@ -603,7 +553,7 @@ accessToUnderlyingConnectionAllowed=false
}
```
-### `GenericObjectPool.borrowObject()`
+### GenericObjectPool.borrowObject()
`GenericObjectPool`是一个很简练的类,里面涉及到的属性设置和锁机制都涉及得非常巧妙。
@@ -738,7 +688,7 @@ accessToUnderlyingConnectionAllowed=false
return p.getObject();
}
```
-### `GenericObjectPool.create()`
+### GenericObjectPool.create()
这里在创建连接对象时采用的锁机制非常值得学习,简练且高效。
@@ -822,7 +772,7 @@ accessToUnderlyingConnectionAllowed=false
```
-### `PoolableConnectionFactory.makeObject()`
+### PoolableConnectionFactory.makeObject()
```java
public PooledObject makeObject() throws Exception {
// 创建原生的Connection对象
@@ -890,10 +840,11 @@ accessToUnderlyingConnectionAllowed=false
}
```
-## 空闲对象回收器`Evictor`
-以上基本已分析完连接对象的获取过程,下面再研究下空闲对象回收器。前面已经讲到当创建完数据源对象时会开启连接池的`evictor`线程,所以我们从`BasicDataSource.startPoolMaintenance()`开始分析。
+## 空闲对象回收器Evictor
+
+以上基本已分析完连接对象的获取过程,下面再研究下空闲对象回收器。前面已经讲到当创建完数据源对象时会开启连接池的`evictor`线程,所以我们从`BasicDataSource.startPoolMaintenance()`开始分析。
-### `BasicDataSource.startPoolMaintenance()`
+### BasicDataSource.startPoolMaintenance()
前面说过`timeBetweenEvictionRunsMillis`为非正数时不会开启开启空闲对象回收器,从以下代码可以理解具体逻辑。
@@ -905,8 +856,9 @@ accessToUnderlyingConnectionAllowed=false
}
}
```
-### `BaseGenericObjectPool.setTimeBetweenEvictionRunsMillis(long)`
-这个`BaseGenericObjectPool`是上面说到的`GenericObjectPool`的父类。
+### BaseGenericObjectPool.setTimeBetweenEvictionRunsMillis(long)
+
+这个`BaseGenericObjectPool`是上面说到的`GenericObjectPool`的父类。
```java
public final void setTimeBetweenEvictionRunsMillis(
@@ -918,9 +870,9 @@ accessToUnderlyingConnectionAllowed=false
}
```
-### `BaseGenericObjectPool.startEvictor(long)`
+### BaseGenericObjectPool.startEvictor(long)
-这里会去定义一个`Evictor`对象,这个其实是一个Runnable对象,后面会讲到。
+这里会去定义一个`Evictor`对象,这个其实是一个`Runnable`对象,后面会讲到。
```java
final void startEvictor(final long delay) {
@@ -939,7 +891,8 @@ accessToUnderlyingConnectionAllowed=false
}
```
-### `EvictionTimer.schedule(Evictor, long, long)`
+### EvictionTimer.schedule(Evictor, long, long)
+
`DBCP`是使用`ScheduledThreadPoolExecutor`来实现回收器的定时检测。 涉及到`ThreadPoolExecutor`为`JDK`自带的`api`,这里不再深入分析线程池如何实现定时调度。感兴趣的朋友可以复习下常用的几款线程池。
```java
@@ -957,8 +910,9 @@ accessToUnderlyingConnectionAllowed=false
task.setScheduledFuture(scheduledFuture);
}
```
-### `BaseGenericObjectPool.Evictor`
-`Evictor`是`BaseGenericObjectPool`的内部类,实现了`Runnable`接口,这里看下它的run方法。
+### BaseGenericObjectPool.Evictor
+
+`Evictor`是`BaseGenericObjectPool`的内部类,实现了`Runnable`接口,这里看下它的run方法。
```java
class Evictor implements Runnable {
@@ -1014,8 +968,9 @@ accessToUnderlyingConnectionAllowed=false
}
```
-### `GenericObjectPool.evict()`
-这里的回收过程包括以下四道校验:
+### GenericObjectPool.evict()
+
+这里的回收过程包括以下四道校验:
1. 按照`evictionPolicy`校验`idleSoftEvictTime`、`idleEvictTime`;
@@ -1132,7 +1087,7 @@ accessToUnderlyingConnectionAllowed=false
-# 通过`JNDI`获取数据源对象
+# 通过JNDI获取数据源对象
## 需求
@@ -1149,7 +1104,9 @@ accessToUnderlyingConnectionAllowed=false
| `SharedPoolDataSource` | `InstanceKeyDataSource`的子类,不同用户共享一个连接池 |
## 引入依赖
-本文在前面例子的基础上增加以下依赖,因为是web项目,所以打包方式为`war`:
+
+本文在前面例子的基础上增加以下依赖,因为是web项目,所以打包方式为`war`:
+
```xml
javax.servlet
@@ -1171,7 +1128,7 @@ accessToUnderlyingConnectionAllowed=false
```
-## 编写`context.xml`
+## 编写context.xml
在`webapp`文件下创建目录`META-INF`,并创建`context.xml`文件。这里面的每个`resource`节点都是我们配置的对象,类似于`spring`的`bean`节点。其中`bean/DriverAdapterCPDS`这个对象需要被另外两个使用到。
@@ -1221,7 +1178,7 @@ accessToUnderlyingConnectionAllowed=false
```
-## 编写`web.xml`
+## 编写web.xml
在`web-app`节点下配置资源引用,每个`resource-env-ref`指向了我们配置好的对象。
@@ -1243,7 +1200,7 @@ accessToUnderlyingConnectionAllowed=false
```
-## 编写`jsp`
+## 编写jsp
因为需要在`web`环境中使用,如果直接建类写个`main`方法测试,会一直报错的,目前没找到好的办法。这里就简单地使用`jsp`来测试吧(这是从tomcat官网参照的例子)。
@@ -1282,7 +1239,7 @@ conn2=128868782, URL=jdbc:mysql://localhost:3306/github_demo?useUnicode=true&cha
-# 使用`DBCP`测试两阶段提交
+# 使用DBCP测试两阶段提交
前面源码分析已经讲到,以下类用于支持`JTA`事务。本文将介绍如何使用`DBCP`来实现`JTA`事务两阶段提交(当然,实际项目并不支持使用`2PC`,因为性能开销太大)。
@@ -1302,7 +1259,7 @@ SET innodb_support_xa = ON -- 开启XA事务
除了原来的`github_demo`数据库,我另外建了一个`test`数据库,简单地模拟两个数据库。
-## `mysql`的`XA`事务使用
+## mysql的XA事务使用
测试之前,这里简单回顾下直接使用`sql`操作`XA`事务的过程,将有助于对以下内容的理解:
@@ -1334,7 +1291,7 @@ XA RECOVER; -- 查看处于prepare状态的事务列表
```
-## 获取`BasicManagedDataSource`
+## 获取BasicManagedDataSource
这里千万记得要设置`DefaultCatalog`,否则当前事务中注册不同资源管理器时,可能都会被当成同一个资源管理器而拒绝注册并报错,因为这个问题,花了我好长时间才解决。
diff --git a/src/main/java/cn/zzs/dbcp/BasicDataSourceUtil.java b/src/main/java/cn/zzs/dbcp/JDBCUtils.java
similarity index 95%
rename from src/main/java/cn/zzs/dbcp/BasicDataSourceUtil.java
rename to src/main/java/cn/zzs/dbcp/JDBCUtils.java
index 18880c7..a333d19 100644
--- a/src/main/java/cn/zzs/dbcp/BasicDataSourceUtil.java
+++ b/src/main/java/cn/zzs/dbcp/JDBCUtils.java
@@ -19,7 +19,7 @@ import org.apache.commons.logging.LogFactory;
* @author: zzs
* @date: 2019年8月31日 下午9:05:08
*/
-public class BasicDataSourceUtil {
+public class JDBCUtils {
private static DataSource dataSource;
@@ -27,7 +27,7 @@ public class BasicDataSourceUtil {
private static final Object obj = new Object();
- private static final Log log = LogFactory.getLog(BasicDataSourceUtil.class);
+ private static final Log log = LogFactory.getLog(JDBCUtils.class);
static {
init();
@@ -172,7 +172,7 @@ public class BasicDataSourceUtil {
private static void init() {
// 导入配置文件
Properties properties = new Properties();
- InputStream in = BasicDataSourceUtil.class.getClassLoader().getResourceAsStream("jdbc.properties");
+ InputStream in = JDBCUtils.class.getClassLoader().getResourceAsStream("dbcp.properties");
try {
properties.load(in);
// 根据配置文件内容获得数据源对象
diff --git a/src/main/resources/jdbc.properties b/src/main/resources/dbcp.properties
similarity index 96%
rename from src/main/resources/jdbc.properties
rename to src/main/resources/dbcp.properties
index edba00a..f100f2a 100644
--- a/src/main/resources/jdbc.properties
+++ b/src/main/resources/dbcp.properties
@@ -1,10 +1,10 @@
-#\u6570\u636e\u5e93\u57fa\u672c\u914d\u7f6e
+#\u57fa\u672c\u8fde\u63a5\u5c5e\u6027
driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/github_demo?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&useSSL=true
username=root
password=root
-#-------------\u8fde\u63a5\u6c60\u6570\u636e\u76f8\u5173\u53c2\u6570--------------------------------
+#-------------\u8fde\u63a5\u6c60\u5927\u5c0f\u548c\u8fde\u63a5\u8d85\u65f6\u53c2\u6570--------------------------------
#\u521d\u59cb\u5316\u8fde\u63a5\u6570\u91cf:\u8fde\u63a5\u6c60\u542f\u52a8\u65f6\u521b\u5efa\u7684\u521d\u59cb\u5316\u8fde\u63a5\u6570\u91cf
#\u9ed8\u8ba4\u4e3a0
initialSize=0
@@ -27,6 +27,11 @@ minIdle=0
#\u9ed8\u8ba4-1
maxWaitMillis=-1
+#\u8fde\u63a5\u6c60\u521b\u5efa\u7684\u8fde\u63a5\u7684\u9ed8\u8ba4\u7684\u6570\u636e\u5e93\u540d\uff0c\u5982\u679c\u662f\u4f7f\u7528DBCP\u7684XA\u8fde\u63a5\u5fc5\u987b\u8bbe\u7f6e\uff0c\u4e0d\u7136\u6ce8\u518c\u4e0d\u4e86\u591a\u4e2a\u8d44\u6e90\u7ba1\u7406\u5668
+#defaultCatalog=github_demo
+
+#\u8fde\u63a5\u6c60\u521b\u5efa\u7684\u8fde\u63a5\u7684\u9ed8\u8ba4\u7684schema\u3002\u5982\u679c\u662fmysql\uff0c\u8fd9\u4e2a\u8bbe\u7f6e\u6ca1\u4ec0\u4e48\u7528\u3002
+#defaultSchema=github_demo
#-------------\u7f13\u5b58\u8bed\u53e5--------------------------------
#\u662f\u5426\u7f13\u5b58preparedStatement\uff0c\u4e5f\u5c31\u662fPSCache\u3002
@@ -118,11 +123,6 @@ autoCommitOnReturn=true
#\u9ed8\u8ba4true
rollbackOnReturn=true
-#\u8fde\u63a5\u6c60\u521b\u5efa\u7684\u8fde\u63a5\u7684\u9ed8\u8ba4\u7684\u6570\u636e\u5e93\u540d\uff0c\u5982\u679c\u662f\u4f7f\u7528DBCP\u7684XA\u8fde\u63a5\u5fc5\u987b\u8bbe\u7f6e\uff0c\u4e0d\u7136\u6ce8\u518c\u4e0d\u4e86\u591a\u4e2a\u8d44\u6e90\u7ba1\u7406\u5668
-#defaultCatalog=github_demo
-
-#\u8fde\u63a5\u6c60\u521b\u5efa\u7684\u8fde\u63a5\u7684\u9ed8\u8ba4\u7684schema\u3002\u5982\u679c\u662fmysql\uff0c\u8fd9\u4e2a\u8bbe\u7f6e\u6ca1\u4ec0\u4e48\u7528\u3002
-#defaultSchema=github_demo
#-------------\u8fde\u63a5\u6cc4\u6f0f\u56de\u6536\u53c2\u6570--------------------------------
#\u5f53\u672a\u4f7f\u7528\u7684\u65f6\u95f4\u8d85\u8fc7removeAbandonedTimeout\u65f6\uff0c\u662f\u5426\u89c6\u8be5\u8fde\u63a5\u4e3a\u6cc4\u9732\u8fde\u63a5\u5e76\u5220\u9664\uff08\u5f53getConnection()\u88ab\u8c03\u7528\u65f6\u68c0\u6d4b\uff09
diff --git a/src/main/resources/log4j.properties b/src/main/resources/log4j.properties
index c38440f..94c0fb2 100644
--- a/src/main/resources/log4j.properties
+++ b/src/main/resources/log4j.properties
@@ -1,8 +1,8 @@
#OFF,systemOut,logFile,logDailyFile,logRollingFile,logMail,logDB,ALL
#\u2460\u914d\u7f6e\u6839Logger
-log4j.rootLogger=info,systemOut
-log4j.logger.cn.zzs.dbcp=debug,systemOut
-log4j.additivity.cn.zzs.dbcp=false
+log4j.rootLogger=debug,systemOut
+#log4j.logger.cn.zzs.dbcp=debug,systemOut
+#log4j.additivity.cn.zzs.dbcp=false
#\u8f93\u51fa\u5230\u63a7\u5236\u53f0
log4j.appender.systemOut= org.apache.log4j.ConsoleAppender
diff --git a/src/main/resources/uml/BasicDataSourceUML b/src/main/resources/uml/BasicDataSourceUML
new file mode 100644
index 0000000..daca97f
--- /dev/null
+++ b/src/main/resources/uml/BasicDataSourceUML
@@ -0,0 +1,36 @@
+@startuml
+class BasicDataSource{
+ - volatile GenericObjectPool connectionPool
+ - volatile DataSource dataSource
+}
+interface DataSource
+interface BasicDataSourceMXBean
+interface MBeanRegistration
+
+class PoolingDataSource{
+ - final ObjectPool pool
+}
+
+class ManagedDataSource{
+ - TransactionRegistry transactionRegistry
+}
+
+class BasicManagedDataSource{
+ - TransactionRegistry transactionRegistry
+ - TransactionManager transactionManager
+ - XADataSource xaDataSourceInstance
+}
+
+BasicDataSource .up.|> DataSource
+BasicDataSource .up.|> BasicDataSourceMXBean
+BasicDataSource .up.|> MBeanRegistration
+
+BasicManagedDataSource -up-|> BasicDataSource
+
+PoolingDataSource .up.|> DataSource
+PoolingDataSource -left-* BasicDataSource
+
+ManagedDataSource -up-|> PoolingDataSource
+ManagedDataSource -left-* BasicManagedDataSource
+
+@enduml
\ No newline at end of file
diff --git a/src/main/resources/uml/DelegatingConnectionUML b/src/main/resources/uml/DelegatingConnectionUML
new file mode 100644
index 0000000..b1e761b
--- /dev/null
+++ b/src/main/resources/uml/DelegatingConnectionUML
@@ -0,0 +1,35 @@
+@startuml
+interface Connection
+class DelegatingConnection{
+ - volatile C connection
+}
+
+class PoolingConnection
+
+class PoolableConnection
+
+class ManagedConnection
+
+class PoolableManagedConnection
+
+class PoolGuardConnectionWrapper
+
+DelegatingConnection .up.|> Connection
+
+PoolingConnection -up-|> DelegatingConnection
+
+PoolingConnection .left.* PoolableConnection
+
+PoolableConnection -up-|> DelegatingConnection
+
+ManagedConnection -up-|> DelegatingConnection
+
+PoolableManagedConnection -up-|> PoolableConnection
+
+ManagedConnection .left.* PoolableManagedConnection
+
+PoolGuardConnectionWrapper -up-|> DelegatingConnection
+
+PoolableConnection .left.* PoolGuardConnectionWrapper
+
+@enduml
\ No newline at end of file
diff --git a/src/main/resources/uml/GenericObjectPoolUML b/src/main/resources/uml/GenericObjectPoolUML
new file mode 100644
index 0000000..8c53ab4
--- /dev/null
+++ b/src/main/resources/uml/GenericObjectPoolUML
@@ -0,0 +1,40 @@
+@startuml
+class PoolingDataSource{
+ - final ObjectPool pool
+}
+
+class GenericObjectPool{
+ - final PooledObjectFactory factory
+ - final Map, PooledObject> allObjects
+ - final LinkedBlockingDeque> idleObjects
+ - Evictor evictor
+ + T borrowObject()
+ + void returnObject(final T obj)
+}
+
+interface ConnectionFactory
+class DriverConnectionFactory{
+ + Connection createConnection()
+}
+
+interface PooledObjectFactory
+class PoolableConnectionFactory{
+ - final ConnectionFactory connectionFactory
+ - volatile ObjectPool pool
+ + PooledObject makeObject()
+ + void destroyObject(PooledObject p)
+ + boolean validateObject(PooledObject p)
+ + void activateObject(PooledObject p)
+}
+
+GenericObjectPool -down-* PoolingDataSource
+
+DriverConnectionFactory .up.|> ConnectionFactory
+
+PoolableConnectionFactory .up.|> PooledObjectFactory
+
+DriverConnectionFactory -down-* PoolableConnectionFactory
+
+PoolableConnectionFactory -left-* GenericObjectPool
+PoolableConnectionFactory *-right- GenericObjectPool
+@enduml
\ No newline at end of file
diff --git a/src/test/java/cn/zzs/dbcp/BasicDataSourceTest.java b/src/test/java/cn/zzs/dbcp/BasicDataSourceTest.java
index 724518b..21e633a 100644
--- a/src/test/java/cn/zzs/dbcp/BasicDataSourceTest.java
+++ b/src/test/java/cn/zzs/dbcp/BasicDataSourceTest.java
@@ -30,9 +30,9 @@ public class BasicDataSourceTest {
PreparedStatement statement = null;
try {
// 获得连接
- connection = BasicDataSourceUtil.getConnection();
+ connection = JDBCUtils.getConnection();
// 开启事务设置非自动提交
- BasicDataSourceUtil.startTrasaction();
+ JDBCUtils.startTrasaction();
// 获得Statement对象
statement = connection.prepareStatement(sql);
// 设置参数
@@ -44,13 +44,13 @@ public class BasicDataSourceTest {
// 执行
statement.executeUpdate();
// 提交事务
- BasicDataSourceUtil.commit();
+ JDBCUtils.commit();
} catch(Exception e) {
- BasicDataSourceUtil.rollback();
+ JDBCUtils.rollback();
log.error("保存用户失败", e);
} finally {
// 释放资源
- BasicDataSourceUtil.release(connection, statement, null);
+ JDBCUtils.release(connection, statement, null);
}
}
@@ -65,9 +65,9 @@ public class BasicDataSourceTest {
PreparedStatement statement = null;
try {
// 获得连接
- connection = BasicDataSourceUtil.getConnection();
+ connection = JDBCUtils.getConnection();
// 开启事务
- BasicDataSourceUtil.startTrasaction();
+ JDBCUtils.startTrasaction();
// 获得Statement对象
statement = connection.prepareStatement(sql);
// 设置参数
@@ -77,13 +77,13 @@ public class BasicDataSourceTest {
// 执行
statement.executeUpdate();
// 提交事务
- BasicDataSourceUtil.commit();
+ JDBCUtils.commit();
} catch(Exception e) {
log.error("异常导致操作回滚", e);
- BasicDataSourceUtil.rollback();
+ JDBCUtils.rollback();
} finally {
// 释放资源
- BasicDataSourceUtil.release(connection, statement, null);
+ JDBCUtils.release(connection, statement, null);
}
}
@@ -99,7 +99,7 @@ public class BasicDataSourceTest {
ResultSet resultSet = null;
try {
// 获得连接
- connection = BasicDataSourceUtil.getConnection();
+ connection = JDBCUtils.getConnection();
// 获得Statement对象
statement = connection.prepareStatement(sql);
// 执行
@@ -114,7 +114,7 @@ public class BasicDataSourceTest {
log.error("查询用户异常", e);
} finally {
// 释放资源
- BasicDataSourceUtil.release(connection, statement, resultSet);
+ JDBCUtils.release(connection, statement, resultSet);
}
}
@@ -129,9 +129,9 @@ public class BasicDataSourceTest {
PreparedStatement statement = null;
try {
// 获得连接
- connection = BasicDataSourceUtil.getConnection();
+ connection = JDBCUtils.getConnection();
// 设置非自动提交
- BasicDataSourceUtil.startTrasaction();
+ JDBCUtils.startTrasaction();
// 获得Statement对象
statement = connection.prepareStatement(sql);
// 设置参数
@@ -139,13 +139,14 @@ public class BasicDataSourceTest {
// 执行
statement.executeUpdate();
// 提交事务
- BasicDataSourceUtil.commit();
+ JDBCUtils.commit();
} catch(Exception e) {
log.error("异常导致操作回滚", e);
- BasicDataSourceUtil.rollback();
+ JDBCUtils.rollback();
} finally {
// 释放资源
- BasicDataSourceUtil.release(connection, statement, null);
+ JDBCUtils.release(connection, statement, null);
}
}
+
}