You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

README.md 60 kB

6 years ago
6 years ago
6 years ago
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448
  1. # Table of Contents
  2. * [简介](#简介)
  3. * [使用例子](#使用例子)
  4. * [需求](#需求)
  5. * [工程环境](#工程环境)
  6. * [主要步骤](#主要步骤)
  7. * [创建项目](#创建项目)
  8. * [引入依赖](#引入依赖)
  9. * [编写`dbcp.prperties`](#编写dbcpprperties)
  10. * [获取连接池和获取连接](#获取连接池和获取连接)
  11. * [编写测试类](#编写测试类)
  12. * [配置文件详解](#配置文件详解)
  13. * [基本连接属性](#基本连接属性)
  14. * [连接池大小参数](#连接池大小参数)
  15. * [缓存语句](#缓存语句)
  16. * [连接检查参数](#连接检查参数)
  17. * [事务相关参数](#事务相关参数)
  18. * [连接泄漏回收参数](#连接泄漏回收参数)
  19. * [其他](#其他)
  20. * [源码分析](#源码分析)
  21. * [创建数据源和连接池](#创建数据源和连接池)
  22. * [BasicDataSource.getConnection()](#basicdatasourcegetconnection)
  23. * [BasicDataSource.createDataSource()](#basicdatasourcecreatedatasource)
  24. * [获取连接对象](#获取连接对象)
  25. * [PoolingDataSource.getConnection()](#poolingdatasourcegetconnection)
  26. * [GenericObjectPool.borrowObject()](#genericobjectpoolborrowobject)
  27. * [GenericObjectPool.create()](#genericobjectpoolcreate)
  28. * [PoolableConnectionFactory.makeObject()](#poolableconnectionfactorymakeobject)
  29. * [空闲对象回收器Evictor](#空闲对象回收器evictor)
  30. * [BasicDataSource.startPoolMaintenance()](#basicdatasourcestartpoolmaintenance)
  31. * [BaseGenericObjectPool.setTimeBetweenEvictionRunsMillis(long)](#basegenericobjectpoolsettimebetweenevictionrunsmillislong)
  32. * [BaseGenericObjectPool.startEvictor(long)](#basegenericobjectpoolstartevictorlong)
  33. * [EvictionTimer.schedule(Evictor, long, long)](#evictiontimerscheduleevictor-long-long)
  34. * [BaseGenericObjectPool.Evictor](#basegenericobjectpoolevictor)
  35. * [GenericObjectPool.evict()](#genericobjectpoolevict)
  36. * [通过JNDI获取数据源对象](#通过jndi获取数据源对象)
  37. * [需求](#需求-1)
  38. * [引入依赖](#引入依赖-1)
  39. * [编写context.xml](#编写contextxml)
  40. * [编写web.xml](#编写webxml)
  41. * [编写jsp](#编写jsp)
  42. * [测试结果](#测试结果)
  43. * [使用DBCP测试两阶段提交](#使用dbcp测试两阶段提交)
  44. * [准备工作](#准备工作)
  45. * [mysql的XA事务使用](#mysql的xa事务使用)
  46. * [引入依赖](#引入依赖-2)
  47. * [获取BasicManagedDataSource](#获取basicmanageddatasource)
  48. * [编写两阶段提交的代码](#编写两阶段提交的代码)
  49. # 简介
  50. `DBCP`用于创建和管理连接,利用“池”的方式复用连接减少资源开销,和其他连接池一样,也具有连接数控制、连接有效性检测、连接泄露控制、缓存语句等功能。目前,`tomcat`自带的连接池就是`DBCP`,Spring开发组也推荐使用`DBCP`,阿里的`druid`也是参照`DBCP`开发出来的。
  51. `DBCP`除了我们熟知的使用方式外,还支持通过`JNDI`获取数据源,并支持获取`JTA`或`XA`事务中用于`2PC`(两阶段提交)的连接对象,本文也将以例子说明。
  52. 本文将包含以下内容(因为篇幅较长,可根据需要选择阅读):
  53. 1. `DBCP`的使用方法(入门案例说明);
  54. 2. `DBCP`的配置参数详解;
  55. 3. `DBCP`主要源码分析;
  56. 4. `DBCP`其他特性的使用方法,如`JNDI`和`JTA`支持。
  57. # 使用例子
  58. ## 需求
  59. 使用`DBCP`连接池获取连接对象,对用户数据进行简单的增删改查。
  60. ## 工程环境
  61. `JDK`:1.8.0_201
  62. `maven`:3.6.1
  63. `IDE`:eclipse 4.12
  64. `mysql-connector-java`:8.0.15
  65. `mysql`:5.7.28
  66. `DBCP`:2.6.0
  67. ## 主要步骤
  68. 1. 编写`dbcp.properties`,设置数据库连接参数和连接池基本参数等。
  69. 2. 通过`BasicDataSourceFactory`加载`dbcp.properties`,并获得`BasicDataDource`对象。
  70. 3. 通过`BasicDataDource`对象获取`Connection`对象。
  71. 4. 使用`Connection`对象对用户表进行增删改查。
  72. ## 创建项目
  73. 项目类型Maven Project,打包方式war(其实jar也可以,之所以使用war是为了测试`JNDI`)。
  74. ## 引入依赖
  75. ```xml
  76. <!-- junit -->
  77. <dependency>
  78. <groupId>junit</groupId>
  79. <artifactId>junit</artifactId>
  80. <version>4.12</version>
  81. <scope>test</scope>
  82. </dependency>
  83. <!-- dbcp -->
  84. <dependency>
  85. <groupId>org.apache.commons</groupId>
  86. <artifactId>commons-dbcp2</artifactId>
  87. <version>2.6.0</version>
  88. </dependency>
  89. <!-- log4j -->
  90. <dependency>
  91. <groupId>log4j</groupId>
  92. <artifactId>log4j</artifactId>
  93. <version>1.2.17</version>
  94. </dependency>
  95. <!-- mysql驱动的jar包 -->
  96. <dependency>
  97. <groupId>mysql</groupId>
  98. <artifactId>mysql-connector-java</artifactId>
  99. <version>8.0.15</version>
  100. </dependency>
  101. ```
  102. ## 编写`dbcp.prperties`
  103. 路径`resources`目录下,因为是入门例子,这里仅给出数据库连接参数和连接池基本参数,后面源码会对配置参数进行详细说明。另外,数据库`sql`脚本也在该目录下。
  104. ```properties
  105. #连接基本属性
  106. driverClassName=com.mysql.cj.jdbc.Driver
  107. url=jdbc:mysql://localhost:3306/github_demo?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&useSSL=true
  108. username=root
  109. password=root
  110. #-------------连接池大小和连接超时参数--------------------------------
  111. #初始化连接数量:连接池启动时创建的初始化连接数量
  112. #默认为0
  113. initialSize=0
  114. #最大活动连接数量:连接池在同一时间能够分配的最大活动连接的数量, 如果设置为负数则表示不限制
  115. #默认为8
  116. maxTotal=8
  117. #最大空闲连接:连接池中容许保持空闲状态的最大连接数量,超过的空闲连接将被释放,如果设置为负数表示不限制
  118. #默认为8
  119. maxIdle=8
  120. #最小空闲连接:连接池中容许保持空闲状态的最小连接数量,低于这个数量将创建新的连接,如果设置为0则不创建
  121. #注意:timeBetweenEvictionRunsMillis为正数时,这个参数才能生效。
  122. #默认为0
  123. minIdle=0
  124. #最大等待时间
  125. #当没有可用连接时,连接池等待连接被归还的最大时间(以毫秒计数),超过时间则抛出异常,如果设置为<=0表示无限等待
  126. #默认-1
  127. maxWaitMillis=-1
  128. ```
  129. ## 获取连接池和获取连接
  130. 项目中编写了`JDBCUtils`来初始化连接池、获取连接、管理事务和释放资源等,具体参见项目源码。
  131. 路径:`cn.zzs.dbcp`
  132. ```java
  133. // 导入配置文件
  134. Properties properties = new Properties();
  135. InputStream in = JDBCUtil.class.getClassLoader().getResourceAsStream("dbcp.properties");
  136. properties.load(in);
  137. // 根据配置文件内容获得数据源对象
  138. DataSource dataSource = BasicDataSourceFactory.createDataSource(properties);
  139. // 获得连接
  140. Connection conn = dataSource.getConnection();
  141. ```
  142. ## 编写测试类
  143. 这里以保存用户为例,路径test目录下的`cn.zzs.dbcp`。
  144. ```java
  145. @Test
  146. public void save() {
  147. // 创建sql
  148. String sql = "insert into demo_user values(null,?,?,?,?,?)";
  149. Connection connection = null;
  150. PreparedStatement statement = null;
  151. try {
  152. // 获得连接
  153. connection = JDBCUtils.getConnection();
  154. // 开启事务设置非自动提交
  155. JDBCUtils.startTrasaction();
  156. // 获得Statement对象
  157. statement = connection.prepareStatement(sql);
  158. // 设置参数
  159. statement.setString(1, "zzf003");
  160. statement.setInt(2, 18);
  161. statement.setDate(3, new Date(System.currentTimeMillis()));
  162. statement.setDate(4, new Date(System.currentTimeMillis()));
  163. statement.setBoolean(5, false);
  164. // 执行
  165. statement.executeUpdate();
  166. // 提交事务
  167. JDBCUtils.commit();
  168. } catch(Exception e) {
  169. JDBCUtils.rollback();
  170. log.error("保存用户失败", e);
  171. } finally {
  172. // 释放资源
  173. JDBCUtils.release(connection, statement, null);
  174. }
  175. }
  176. ```
  177. # 配置文件详解
  178. 这部分内容从网上参照过来,同样的内容发的到处都是,暂时没找到出处。因为内容太过杂乱,而且最新版本更新了不少内容,所以我花了好大功夫才改好,后面找到出处再补上参考资料吧。
  179. ## 基本连接属性
  180. 注意,这里在`url`后面拼接了多个参数用于避免乱码、时区报错问题。 补充下,如果不想加入时区的参数,可以在`mysql`命令窗口执行如下命令:`set global time_zone='+8:00'`。
  181. ```properties
  182. driverClassName=com.mysql.cj.jdbc.Driver
  183. url=jdbc:mysql://localhost:3306/github_demo?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&useSSL=true
  184. username=root
  185. password=root
  186. ```
  187. ## 连接池大小参数
  188. 这几个参数都比较常用,具体设置多少需根据项目调整。
  189. ```properties
  190. #-------------连接池大小和连接超时参数--------------------------------
  191. #初始化连接数量:连接池启动时创建的初始化连接数量
  192. #默认为0
  193. initialSize=0
  194. #最大活动连接数量:连接池在同一时间能够分配的最大活动连接的数量, 如果设置为负数则表示不限制
  195. #默认为8
  196. maxTotal=8
  197. #最大空闲连接:连接池中容许保持空闲状态的最大连接数量,超过的空闲连接将被释放,如果设置为负数表示不限制
  198. #默认为8
  199. maxIdle=8
  200. #最小空闲连接:连接池中容许保持空闲状态的最小连接数量,低于这个数量将创建新的连接,如果设置为0则不创建
  201. #注意:timeBetweenEvictionRunsMillis为正数时,这个参数才能生效。
  202. #默认为0
  203. minIdle=0
  204. #最大等待时间
  205. #当没有可用连接时,连接池等待连接被归还的最大时间(以毫秒计数),超过时间则抛出异常,如果设置为<=0表示无限等待
  206. #默认-1
  207. maxWaitMillis=-1
  208. #连接池创建的连接的默认的数据库名,如果是使用DBCP的XA连接必须设置,不然注册不了多个资源管理器
  209. #defaultCatalog=github_demo
  210. #连接池创建的连接的默认的schema。如果是mysql,这个设置没什么用。
  211. #defaultSchema=github_demo
  212. ```
  213. ## 缓存语句
  214. 缓存语句在`mysql`下建议关闭。
  215. ```properties
  216. #-------------缓存语句--------------------------------
  217. #是否缓存preparedStatement,也就是PSCache。
  218. #PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭
  219. #默认为false
  220. poolPreparedStatements=false
  221. #缓存PreparedStatements的最大个数
  222. #默认为-1
  223. #注意:poolPreparedStatements为true时,这个参数才有效
  224. maxOpenPreparedStatements=-1
  225. #缓存read-only和auto-commit状态。设置为true的话,所有连接的状态都会是一样的。
  226. #默认是true
  227. cacheState=true
  228. ```
  229. ## 连接检查参数
  230. 针对连接失效和连接泄露的问题,建议开启`testWhileIdle`,而不是开启`testOnReturn`或`testOnBorrow`(从性能考虑)。
  231. ```properties
  232. #-------------连接检查情况--------------------------------
  233. #通过SQL查询检测连接,注意必须返回至少一行记录
  234. #默认为空。即会调用Connection的isValid和isClosed进行检测
  235. #注意:如果是oracle数据库的话,应该改为select 1 from dual
  236. validationQuery=select 1 from dual
  237. #SQL检验超时时间
  238. validationQueryTimeout=-1
  239. #是否从池中取出连接前进行检验。
  240. #默认为true
  241. testOnBorrow=true
  242. #是否在归还到池中前进行检验
  243. #默认为false
  244. testOnReturn=false
  245. #是否开启空闲资源回收器。
  246. #默认为false
  247. testWhileIdle=false
  248. #空闲资源的检测周期(单位为毫秒)。
  249. #默认-1。即空闲资源回收器不工作。
  250. timeBetweenEvictionRunsMillis=-1
  251. #做空闲资源回收器时,每次的采样数。
  252. #默认3,单位毫秒。如果设置为-1,就是对所有连接做空闲监测。
  253. numTestsPerEvictionRun=3
  254. #资源池中资源最小空闲时间(单位为毫秒),达到此值后将被移除。
  255. #默认值1000*60*30 = 30分钟
  256. minEvictableIdleTimeMillis=1800000
  257. #资源池中资源最小空闲时间(单位为毫秒),达到此值后将被移除。但是会保证minIdle
  258. #默认值-1
  259. #softMinEvictableIdleTimeMillis=-1
  260. #空闲资源回收策略
  261. #默认org.apache.commons.pool2.impl.DefaultEvictionPolicy
  262. #如果要自定义的话,需要实现EvictionPolicy重写evict方法
  263. evictionPolicyClassName=org.apache.commons.pool2.impl.DefaultEvictionPolicy
  264. #连接最大存活时间。非正数表示不限制
  265. #默认-1
  266. maxConnLifetimeMillis=-1
  267. #当达到maxConnLifetimeMillis被关闭时,是否打印相关消息
  268. #默认true
  269. #注意:maxConnLifetimeMillis设置为正数时,这个参数才有效
  270. logExpiredConnections=true
  271. ```
  272. ## 事务相关参数
  273. 这里的参数主要和事务相关,一般默认就行。
  274. ```properties
  275. #-------------事务相关的属性--------------------------------
  276. #连接池创建的连接的默认的auto-commit状态
  277. #默认为空,由驱动决定
  278. defaultAutoCommit=true
  279. #连接池创建的连接的默认的read-only状态。
  280. #默认值为空,由驱动决定
  281. defaultReadOnly=false
  282. #连接池创建的连接的默认的TransactionIsolation状态
  283. #可用值为下列之一:NONE,READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE
  284. #默认值为空,由驱动决定
  285. defaultTransactionIsolation=REPEATABLE_READ
  286. #归还连接时是否设置自动提交为true
  287. #默认true
  288. autoCommitOnReturn=true
  289. #归还连接时是否设置回滚事务
  290. #默认true
  291. rollbackOnReturn=true
  292. ```
  293. ## 连接泄漏回收参数
  294. 当我们从连接池获得了连接对象,但因为疏忽或其他原因没有`close`,这个时候这个连接对象就是一个泄露资源。通过配置以下参数可以回收这部分对象。
  295. ```properties
  296. #-------------连接泄漏回收参数--------------------------------
  297. #当未使用的时间超过removeAbandonedTimeout时,是否视该连接为泄露连接并删除(当getConnection()被调用时检测)
  298. #默认为false
  299. #注意:这个机制在(getNumIdle() < 2) and (getNumActive() > (getMaxActive() - 3))时被触发
  300. removeAbandonedOnBorrow=false
  301. #当未使用的时间超过removeAbandonedTimeout时,是否视该连接为泄露连接并删除(空闲evictor检测)
  302. #默认为false
  303. #注意:当空闲资源回收器开启才生效
  304. removeAbandonedOnMaintenance=false
  305. #泄露的连接可以被删除的超时值, 单位秒
  306. #默认为300
  307. removeAbandonedTimeout=300
  308. #标记当Statement或连接被泄露时是否打印程序的stack traces日志。
  309. #默认为false
  310. logAbandoned=true
  311. #这个不是很懂
  312. #默认为false
  313. abandonedUsageTracking=false
  314. ```
  315. ## 其他
  316. 这部分参数比较少用。
  317. ```properties
  318. #-------------其他--------------------------------
  319. #是否使用快速失败机制
  320. #默认为空,由驱动决定
  321. fastFailValidation=false
  322. #当使用快速失败机制时,设置触发的异常码
  323. #多个code用","隔开
  324. #disconnectionSqlCodes
  325. #borrow连接的顺序
  326. #默认true
  327. lifo=true
  328. #每个连接创建时执行的语句
  329. #connectionInitSqls=
  330. #连接参数:例如username、password、characterEncoding等都可以在这里设置
  331. #多个参数用";"隔开
  332. #connectionProperties=
  333. #指定数据源的jmx名
  334. #jmxName=
  335. #查询超时时间
  336. #默认为空,即根据驱动设置
  337. #defaultQueryTimeout=
  338. #控制PoolGuard是否容许获取底层连接
  339. #默认为false
  340. accessToUnderlyingConnectionAllowed=false
  341. #如果容许则可以使用下面的方式来获取底层物理连接:
  342. # Connection conn = ds.getConnection();
  343. # Connection dconn = ((DelegatingConnection) conn).getInnermostDelegate();
  344. # ...
  345. # conn.close();
  346. ```
  347. # 源码分析
  348. 注意:考虑篇幅和可读性,以下代码经过删减,仅保留所需部分。
  349. ## 创建数据源和连接池
  350. 研究之前,先来看下`BasicDataSource`的`UML`图:
  351. ![BasicDataSource的UML图](https://github.com/ZhangZiSheng001/dbcp-demo/tree/master/img/BasicDataSource.png)
  352. 这里介绍下这几个类的作用:
  353. 类名|描述
  354. -|-
  355. `BasicDataSource`|用于满足基本数据库操作需求的数据源
  356. `BasicManagedDataSource`|`BasicDataSource`的子类,用于创建支持`XA`事务或`JTA`事务的连接
  357. `PoolingDataSource`|`BasicDataSource`中实际调用的数据源,可以说`BasicDataSource`只是封装了`PoolingDataSource`
  358. `ManagedDataSource`|`PoolingDataSource`的子类,用于支持`XA`事务或`JTA`事务的连接。是`BasicManagedDataSource`中实际调用的数据源,可以说`BasicManagedDataSource`只是封装了`ManagedDataSource`
  359. 另外,为了支持`JNDI`,`DBCP`也提供了相应的类。
  360. | 类名 | 描述 |
  361. | ----------------------- | ------------------------------------------------------------ |
  362. | `InstanceKeyDataSource` | 用于支持`JDNI`环境的数据源 |
  363. | `PerUserPoolDataSource` | `InstanceKeyDataSource`的子类,针对每个用户会单独分配一个连接池,每个连接池可以设置不同属性。例如以下需求,相比user,`admin`可以创建更多地连接以保证 |
  364. | `SharedPoolDataSource` | `InstanceKeyDataSource`的子类,不同用户共享一个连接池 |
  365. 本文的源码分析仅会涉及到`BasicDataSource`(包含它封装的`PoolingDataSource`),其他的数据源暂时不扩展。
  366. ### BasicDataSource.getConnection()
  367. `BasicDataSourceFactory`只是简单地`new`了一个`BasicDataSource`对象并初始化配置参数,此时真正的数据源(`PoolingDataSource`)以及连接池(`GenericObjectPool<PoolableConnection>`)并没有创建,而创建的时机为我们第一次调用`getConnection()`的时候。因此,本文直接从`BasicDataSource`的`getConnection()`方法开始分析。
  368. ```java
  369. public Connection getConnection() throws SQLException {
  370. return createDataSource().getConnection();
  371. }
  372. ```
  373. ### BasicDataSource.createDataSource()
  374. 这个方法会创建数据源和连接池,整个过程可以概括为以下几步:
  375. 1. 注册`MBean`,用于支持`JMX`;
  376. 2. 创建连接池对象`GenericObjectPool<PoolableConnection>`;
  377. 3. 创建数据源对象`PoolingDataSource<PoolableConnection>`;
  378. 4. 初始化连接数;
  379. 5. 开启空闲资源回收线程(如果设置`timeBetweenEvictionRunsMillis`为正数)。
  380. ```java
  381. protected DataSource createDataSource() throws SQLException {
  382. if(closed) {
  383. throw new SQLException("Data source is closed");
  384. }
  385. if(dataSource != null) {
  386. return dataSource;
  387. }
  388. synchronized(this) {
  389. if(dataSource != null) {
  390. return dataSource;
  391. }
  392. // 注册MBean,用于支持JMX,这方面的内容不在这里扩展
  393. jmxRegister();
  394. // 创建原生Connection工厂:本质就是持有数据库驱动对象和几个连接参数
  395. final ConnectionFactory driverConnectionFactory = createConnectionFactory();
  396. // 将driverConnectionFactory包装成池化Connection工厂
  397. PoolableConnectionFactory poolableConnectionFactory = createPoolableConnectionFactory(driverConnectionFactory);
  398. // 设置PreparedStatements缓存(其实在这里可以发现,上面创建池化工厂时就设置了缓存,这里没必要再设置一遍)
  399. poolableConnectionFactory.setPoolStatements(poolPreparedStatements);
  400. poolableConnectionFactory.setMaxOpenPreparedStatements(maxOpenPreparedStatements);
  401. // 创建数据库连接池对象GenericObjectPool,用于管理连接
  402. // BasicDataSource将持有GenericObjectPool对象
  403. createConnectionPool(poolableConnectionFactory);
  404. // 创建PoolingDataSource对象
  405. // 该对象持有GenericObjectPool对象的引用
  406. DataSource newDataSource = createDataSourceInstance();
  407. newDataSource.setLogWriter(logWriter);
  408. // 根据我们设置的initialSize创建初始连接
  409. for(int i = 0; i < initialSize; i++) {
  410. connectionPool.addObject();
  411. }
  412. // 开启连接池的evictor线程
  413. startPoolMaintenance();
  414. // 最后BasicDataSource将持有上面创建的PoolingDataSource对象
  415. dataSource = newDataSource;
  416. return dataSource;
  417. }
  418. }
  419. ```
  420. 以上方法涉及到几个类,这里再补充下`UML`图。
  421. ![GenericObjectPool的UML图](https://github.com/ZhangZiSheng001/dbcp-demo/tree/master/img/GenericObjectPool.png)
  422. | 类名 | 描述 |
  423. | --------------------------- | ------------------------------------------------------------ |
  424. | `DriverConnectionFactory` | 用于生成原生的Connection对象 |
  425. | `PoolableConnectionFactory` | 用于生成池化的Connection对象,持有`ConnectionFactory`对象的引用 |
  426. | `GenericObjectPool` | 数据库连接池,用于管理连接。持有`PoolableConnectionFactory`对象的引用 |
  427. ## 获取连接对象
  428. 上面已经大致分析了数据源和连接池对象的获取过程,接下来研究下连接对象的获取。在此之前先了解下`DBCP`中几个`Connection`实现类。
  429. ![DelegatingConnection的UML图](https://github.com/ZhangZiSheng001/dbcp-demo/tree/master/img/DelegatingConnection.png)
  430. 类名|描述
  431. -|-
  432. `DelegatingConnection`|`Connection`实现类,是以下几个类的父类
  433. `PoolingConnection`|用于包装原生的`Connection`,支持缓存`prepareStatement`和`prepareCall`
  434. `PoolableConnection`|用于包装原生的`PoolingConnection`(如果没有开启`poolPreparedStatements`,则包装的只是原生`Connection`),调用`close()`时只是将连接还给连接池
  435. `PoolableManagedConnection`|`PoolableConnection`的子类,用于包装`ManagedConnection`,支持`JTA`和`XA`事务
  436. `ManagedConnection`|用于包装原生的`Connection`,支持`JTA`和`XA`事务
  437. `PoolGuardConnectionWrapper`|用于包装`PoolableConnection`,当`accessToUnderlyingConnectionAllowed`才能获取底层连接对象。我们获取到的就是这个对象
  438. 另外,这里先概括下获得连接的整个过程:
  439. 1. 如果设置了`removeAbandonedOnBorrow`,达到条件会进行检测;
  440. 2. 从连接池中获取连接,如果没有就通过工厂创建(通过`DriverConnectionFactory`创建原生对象,再通过`PoolableConnectionFactory`包装为池化对象);
  441. 3. 通过工厂重新初始化连接对象;
  442. 4. 如果设置了`testOnBorrow`或者`testOnCreate`,会通过工厂校验连接有效性;
  443. 5. 使用`PoolGuardConnectionWrapper`包装连接对象,并返回给客户端
  444. ### PoolingDataSource.getConnection()
  445. 前面已经说过,`BasicDataSource`本质上是调用`PoolingDataSource`的方法来获取连接,所以这里从`PoolingDataSource.getConnection()`开始研究。
  446. 以下代码可知,该方法会从连接池中“借出”连接。
  447. ```java
  448. public Connection getConnection() throws SQLException {
  449. // 这个泛型C指的是PoolableConnection对象
  450. // 调用的是GenericObjectPool的方法返回PoolableConnection对象,这个方法后面会展开
  451. final C conn = pool.borrowObject();
  452. if (conn == null) {
  453. return null;
  454. }
  455. // 包装PoolableConnection对象,当accessToUnderlyingConnectionAllowed为true时,可以使用底层连接
  456. return new PoolGuardConnectionWrapper<>(conn);
  457. }
  458. ```
  459. ### GenericObjectPool.borrowObject()
  460. `GenericObjectPool`是一个很简练的类,里面涉及到的属性设置和锁机制都涉及得非常巧妙。
  461. ```java
  462. // 存放着连接池所有的连接对象(但不包含已经释放的)
  463. private final Map<IdentityWrapper<T>, PooledObject<T>> allObjects =
  464. new ConcurrentHashMap<>();
  465. // 存放着空闲连接对象的阻塞队列
  466. private final LinkedBlockingDeque<PooledObject<T>> idleObjects;
  467. // 为n>1表示当前有n个线程正在创建新连接对象
  468. private long makeObjectCount = 0;
  469. // 创建连接对象时所用的锁
  470. private final Object makeObjectCountLock = new Object();
  471. // 连接对象创建总数量
  472. private final AtomicLong createCount = new AtomicLong(0);
  473. public T borrowObject() throws Exception {
  474. // 如果我们设置了连接获取等待时间,“借出”过程就必须在指定时间内完成
  475. return borrowObject(getMaxWaitMillis());
  476. }
  477. public T borrowObject(final long borrowMaxWaitMillis) throws Exception {
  478. // 校验连接池是否打开状态
  479. assertOpen();
  480. // 如果设置了removeAbandonedOnBorrow,达到触发条件是会遍历所有连接,未使用时长超过removeAbandonedTimeout的将被释放掉(一般可以检测出泄露连接)
  481. final AbandonedConfig ac = this.abandonedConfig;
  482. if (ac != null && ac.getRemoveAbandonedOnBorrow() &&
  483. (getNumIdle() < 2) &&
  484. (getNumActive() > getMaxTotal() - 3) ) {
  485. removeAbandoned(ac);
  486. }
  487. PooledObject<T> p = null;
  488. // 连接数达到maxTotal是否阻塞等待
  489. final boolean blockWhenExhausted = getBlockWhenExhausted();
  490. boolean create;
  491. final long waitTime = System.currentTimeMillis();
  492. // 如果获取的连接对象为空,会再次进入获取
  493. while (p == null) {
  494. create = false;
  495. // 获取空闲队列的第一个元素,如果为空就试图创建新连接
  496. p = idleObjects.pollFirst();
  497. if (p == null) {
  498. // 后面分析这个方法
  499. p = create();
  500. if (p != null) {
  501. create = true;
  502. }
  503. }
  504. // 连接数达到maxTotal且暂时没有空闲连接,这时需要阻塞等待,直到获得空闲队列中的连接或等待超时
  505. if (blockWhenExhausted) {
  506. if (p == null) {
  507. if (borrowMaxWaitMillis < 0) {
  508. // 无限等待
  509. p = idleObjects.takeFirst();
  510. } else {
  511. // 等待maxWaitMillis
  512. p = idleObjects.pollFirst(borrowMaxWaitMillis,
  513. TimeUnit.MILLISECONDS);
  514. }
  515. }
  516. // 这个时候还是没有就只能抛出异常
  517. if (p == null) {
  518. throw new NoSuchElementException(
  519. "Timeout waiting for idle object");
  520. }
  521. } else {
  522. if (p == null) {
  523. throw new NoSuchElementException("Pool exhausted");
  524. }
  525. }
  526. // 如果连接处于空闲状态,会修改连接的state、lastBorrowTime、lastUseTime、borrowedCount等,并返回true
  527. if (!p.allocate()) {
  528. p = null;
  529. }
  530. if (p != null) {
  531. // 利用工厂重新初始化连接对象,这里会去校验连接存活时间、设置lastUsedTime、及其他初始参数
  532. try {
  533. factory.activateObject(p);
  534. } catch (final Exception e) {
  535. try {
  536. destroy(p);
  537. } catch (final Exception e1) {
  538. // Ignore - activation failure is more important
  539. }
  540. p = null;
  541. if (create) {
  542. final NoSuchElementException nsee = new NoSuchElementException(
  543. "Unable to activate object");
  544. nsee.initCause(e);
  545. throw nsee;
  546. }
  547. }
  548. // 根据设置的参数,判断是否检测连接有效性
  549. if (p != null && (getTestOnBorrow() || create && getTestOnCreate())) {
  550. boolean validate = false;
  551. Throwable validationThrowable = null;
  552. try {
  553. // 这里会去校验连接的存活时间是否超过maxConnLifetimeMillis,以及通过SQL去校验执行时间
  554. validate = factory.validateObject(p);
  555. } catch (final Throwable t) {
  556. PoolUtils.checkRethrow(t);
  557. validationThrowable = t;
  558. }
  559. // 如果校验不通过,会释放该对象
  560. if (!validate) {
  561. try {
  562. destroy(p);
  563. destroyedByBorrowValidationCount.incrementAndGet();
  564. } catch (final Exception e) {
  565. // Ignore - validation failure is more important
  566. }
  567. p = null;
  568. if (create) {
  569. final NoSuchElementException nsee = new NoSuchElementException(
  570. "Unable to validate object");
  571. nsee.initCause(validationThrowable);
  572. throw nsee;
  573. }
  574. }
  575. }
  576. }
  577. }
  578. // 更新borrowedCount、idleTimes和waitTimes
  579. updateStatsBorrow(p, System.currentTimeMillis() - waitTime);
  580. return p.getObject();
  581. }
  582. ```
  583. ### GenericObjectPool.create()
  584. 这里在创建连接对象时采用的锁机制非常值得学习,简练且高效。
  585. ```java
  586. private PooledObject<T> create() throws Exception {
  587. int localMaxTotal = getMaxTotal();
  588. if (localMaxTotal < 0) {
  589. localMaxTotal = Integer.MAX_VALUE;
  590. }
  591. final long localStartTimeMillis = System.currentTimeMillis();
  592. final long localMaxWaitTimeMillis = Math.max(getMaxWaitMillis(), 0);
  593. // 创建标识:
  594. // - TRUE: 调用工厂创建返回对象
  595. // - FALSE: 直接返回null
  596. // - null: 继续循环
  597. Boolean create = null;
  598. while (create == null) {
  599. synchronized (makeObjectCountLock) {
  600. final long newCreateCount = createCount.incrementAndGet();
  601. if (newCreateCount > localMaxTotal) {
  602. // 当前池已经达到maxTotal,或者有另外一个线程正在试图创建一个新的连接使之达到容量极限
  603. createCount.decrementAndGet();
  604. if (makeObjectCount == 0) {
  605. // 连接池确实已达到容量极限
  606. create = Boolean.FALSE;
  607. } else {
  608. // 当前另外一个线程正在试图创建一个新的连接使之达到容量极限,此时需要等待
  609. makeObjectCountLock.wait(localMaxWaitTimeMillis);
  610. }
  611. } else {
  612. // 当前连接池容量未到达极限,可以继续创建连接对象
  613. makeObjectCount++;
  614. create = Boolean.TRUE;
  615. }
  616. }
  617. // 当达到maxWaitTimeMillis时不创建连接对象,直接退出循环
  618. if (create == null &&
  619. (localMaxWaitTimeMillis > 0 &&
  620. System.currentTimeMillis() - localStartTimeMillis >= localMaxWaitTimeMillis)) {
  621. create = Boolean.FALSE;
  622. }
  623. }
  624. if (!create.booleanValue()) {
  625. return null;
  626. }
  627. final PooledObject<T> p;
  628. try {
  629. // 调用工厂创建对象,后面对这个方法展开分析
  630. p = factory.makeObject();
  631. } catch (final Throwable e) {
  632. createCount.decrementAndGet();
  633. throw e;
  634. } finally {
  635. synchronized (makeObjectCountLock) {
  636. // 创建标识-1
  637. makeObjectCount--;
  638. // 唤醒makeObjectCountLock锁住的对象
  639. makeObjectCountLock.notifyAll();
  640. }
  641. }
  642. final AbandonedConfig ac = this.abandonedConfig;
  643. if (ac != null && ac.getLogAbandoned()) {
  644. p.setLogAbandoned(true);
  645. // TODO: in 3.0, this can use the method defined on PooledObject
  646. if (p instanceof DefaultPooledObject<?>) {
  647. ((DefaultPooledObject<T>) p).setRequireFullStackTrace(ac.getRequireFullStackTrace());
  648. }
  649. }
  650. // 连接数量+1
  651. createdCount.incrementAndGet();
  652. // 将创建的对象放入allObjects
  653. allObjects.put(new IdentityWrapper<>(p.getObject()), p);
  654. return p;
  655. }
  656. ```
  657. ### PoolableConnectionFactory.makeObject()
  658. ```java
  659. public PooledObject<PoolableConnection> makeObject() throws Exception {
  660. // 创建原生的Connection对象
  661. Connection conn = connectionFactory.createConnection();
  662. if (conn == null) {
  663. throw new IllegalStateException("Connection factory returned null from createConnection");
  664. }
  665. try {
  666. // 执行我们设置的connectionInitSqls
  667. initializeConnection(conn);
  668. } catch (final SQLException sqle) {
  669. // Make sure the connection is closed
  670. try {
  671. conn.close();
  672. } catch (final SQLException ignore) {
  673. // ignore
  674. }
  675. // Rethrow original exception so it is visible to caller
  676. throw sqle;
  677. }
  678. // 连接索引+1
  679. final long connIndex = connectionIndex.getAndIncrement();
  680. // 如果设置了poolPreparedStatements,则创建包装连接为PoolingConnection对象
  681. if (poolStatements) {
  682. conn = new PoolingConnection(conn);
  683. final GenericKeyedObjectPoolConfig<DelegatingPreparedStatement> config = new GenericKeyedObjectPoolConfig<>();
  684. config.setMaxTotalPerKey(-1);
  685. config.setBlockWhenExhausted(false);
  686. config.setMaxWaitMillis(0);
  687. config.setMaxIdlePerKey(1);
  688. config.setMaxTotal(maxOpenPreparedStatements);
  689. if (dataSourceJmxObjectName != null) {
  690. final StringBuilder base = new StringBuilder(dataSourceJmxObjectName.toString());
  691. base.append(Constants.JMX_CONNECTION_BASE_EXT);
  692. base.append(Long.toString(connIndex));
  693. config.setJmxNameBase(base.toString());
  694. config.setJmxNamePrefix(Constants.JMX_STATEMENT_POOL_PREFIX);
  695. } else {
  696. config.setJmxEnabled(false);
  697. }
  698. final PoolingConnection poolingConn = (PoolingConnection) conn;
  699. final KeyedObjectPool<PStmtKey, DelegatingPreparedStatement> stmtPool = new GenericKeyedObjectPool<>(
  700. poolingConn, config);
  701. poolingConn.setStatementPool(stmtPool);
  702. poolingConn.setCacheState(cacheState);
  703. }
  704. // 用于注册连接到JMX
  705. ObjectName connJmxName;
  706. if (dataSourceJmxObjectName == null) {
  707. connJmxName = null;
  708. } else {
  709. connJmxName = new ObjectName(
  710. dataSourceJmxObjectName.toString() + Constants.JMX_CONNECTION_BASE_EXT + connIndex);
  711. }
  712. // 创建PoolableConnection对象
  713. final PoolableConnection pc = new PoolableConnection(conn, pool, connJmxName, disconnectionSqlCodes,
  714. fastFailValidation);
  715. pc.setCacheState(cacheState);
  716. // 包装成连接池所需的对象
  717. return new DefaultPooledObject<>(pc);
  718. }
  719. ```
  720. ## 空闲对象回收器Evictor
  721. 以上基本已分析完连接对象的获取过程,下面再研究下空闲对象回收器。前面已经讲到当创建完数据源对象时会开启连接池的`evictor`线程,所以我们从`BasicDataSource.startPoolMaintenance()`开始分析。
  722. ### BasicDataSource.startPoolMaintenance()
  723. 前面说过`timeBetweenEvictionRunsMillis`为非正数时不会开启开启空闲对象回收器,从以下代码可以理解具体逻辑。
  724. ```java
  725. protected void startPoolMaintenance() {
  726. // 只有timeBetweenEvictionRunsMillis为正数,才会开启空闲对象回收器
  727. if (connectionPool != null && timeBetweenEvictionRunsMillis > 0) {
  728. connectionPool.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
  729. }
  730. }
  731. ```
  732. ### BaseGenericObjectPool.setTimeBetweenEvictionRunsMillis(long)
  733. 这个`BaseGenericObjectPool`是上面说到的`GenericObjectPool`的父类。
  734. ```java
  735. public final void setTimeBetweenEvictionRunsMillis(
  736. final long timeBetweenEvictionRunsMillis) {
  737. // 设置回收线程运行间隔时间
  738. this.timeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis;
  739. // 继续调用本类的方法,下面继续进入方法分析
  740. startEvictor(timeBetweenEvictionRunsMillis);
  741. }
  742. ```
  743. ### BaseGenericObjectPool.startEvictor(long)
  744. 这里会去定义一个`Evictor`对象,这个其实是一个`Runnable`对象,后面会讲到。
  745. ```java
  746. final void startEvictor(final long delay) {
  747. synchronized (evictionLock) {
  748. if (null != evictor) {
  749. EvictionTimer.cancel(evictor, evictorShutdownTimeoutMillis, TimeUnit.MILLISECONDS);
  750. evictor = null;
  751. evictionIterator = null;
  752. }
  753. // 创建回收器任务,并执行定时调度
  754. if (delay > 0) {
  755. evictor = new Evictor();
  756. EvictionTimer.schedule(evictor, delay, delay);
  757. }
  758. }
  759. }
  760. ```
  761. ### EvictionTimer.schedule(Evictor, long, long)
  762. `DBCP`是使用`ScheduledThreadPoolExecutor`来实现回收器的定时检测。 涉及到`ThreadPoolExecutor`为`JDK`自带的`api`,这里不再深入分析线程池如何实现定时调度。感兴趣的朋友可以复习下常用的几款线程池。
  763. ```java
  764. static synchronized void schedule(
  765. final BaseGenericObjectPool<?>.Evictor task, final long delay, final long period)
  766. if (null == executor) {
  767. // 创建线程池,队列为DelayedWorkQueue,corePoolSize为1,maximumPoolSize为无限大
  768. executor = new ScheduledThreadPoolExecutor(1, new EvictorThreadFactory());
  769. // 当任务被取消的同时从等待队列中移除
  770. executor.setRemoveOnCancelPolicy(true);
  771. }
  772. // 设置任务定时调度
  773. final ScheduledFuture<?> scheduledFuture =
  774. executor.scheduleWithFixedDelay(task, delay, period, TimeUnit.MILLISECONDS);
  775. task.setScheduledFuture(scheduledFuture);
  776. }
  777. ```
  778. ### BaseGenericObjectPool.Evictor
  779. `Evictor`是`BaseGenericObjectPool`的内部类,实现了`Runnable`接口,这里看下它的run方法。
  780. ```java
  781. class Evictor implements Runnable {
  782. private ScheduledFuture<?> scheduledFuture;
  783. @Override
  784. public void run() {
  785. final ClassLoader savedClassLoader =
  786. Thread.currentThread().getContextClassLoader();
  787. try {
  788. // 确保回收器使用的类加载器和工厂对象的一样
  789. if (factoryClassLoader != null) {
  790. final ClassLoader cl = factoryClassLoader.get();
  791. if (cl == null) {
  792. cancel();
  793. return;
  794. }
  795. Thread.currentThread().setContextClassLoader(cl);
  796. }
  797. try {
  798. // 回收符合条件的对象,后面继续扩展
  799. evict();
  800. } catch(final Exception e) {
  801. swallowException(e);
  802. } catch(final OutOfMemoryError oome) {
  803. // Log problem but give evictor thread a chance to continue
  804. // in case error is recoverable
  805. oome.printStackTrace(System.err);
  806. }
  807. try {
  808. // 确保最小空闲对象
  809. ensureMinIdle();
  810. } catch (final Exception e) {
  811. swallowException(e);
  812. }
  813. } finally {
  814. Thread.currentThread().setContextClassLoader(savedClassLoader);
  815. }
  816. }
  817. void setScheduledFuture(final ScheduledFuture<?> scheduledFuture) {
  818. this.scheduledFuture = scheduledFuture;
  819. }
  820. void cancel() {
  821. scheduledFuture.cancel(false);
  822. }
  823. }
  824. ```
  825. ### GenericObjectPool.evict()
  826. 这里的回收过程包括以下四道校验:
  827. 1. 按照`evictionPolicy`校验`idleSoftEvictTime`、`idleEvictTime`;
  828. 2. 利用工厂重新初始化样本,这里会校验`maxConnLifetimeMillis`(`testWhileIdle`为true);
  829. 3. 校验`maxConnLifetimeMillis`和`validationQueryTimeout`(`testWhileIdle`为true);
  830. 4. 校验所有连接的未使用时间是否超过r`emoveAbandonedTimeout`(`removeAbandonedOnMaintenance`为true)。
  831. ```java
  832. public void evict() throws Exception {
  833. // 校验当前连接池是否关闭
  834. assertOpen();
  835. if (idleObjects.size() > 0) {
  836. PooledObject<T> underTest = null;
  837. // 介绍参数时已经讲到,这个evictionPolicy我们可以自定义
  838. final EvictionPolicy<T> evictionPolicy = getEvictionPolicy();
  839. synchronized (evictionLock) {
  840. final EvictionConfig evictionConfig = new EvictionConfig(
  841. getMinEvictableIdleTimeMillis(),
  842. getSoftMinEvictableIdleTimeMillis(),
  843. getMinIdle());
  844. final boolean testWhileIdle = getTestWhileIdle();
  845. // 获取我们指定的样本数,并开始遍历
  846. for (int i = 0, m = getNumTests(); i < m; i++) {
  847. if (evictionIterator == null || !evictionIterator.hasNext()) {
  848. evictionIterator = new EvictionIterator(idleObjects);
  849. }
  850. if (!evictionIterator.hasNext()) {
  851. // Pool exhausted, nothing to do here
  852. return;
  853. }
  854. try {
  855. underTest = evictionIterator.next();
  856. } catch (final NoSuchElementException nsee) {
  857. // 当前样本正被另一个线程借出
  858. i--;
  859. evictionIterator = null;
  860. continue;
  861. }
  862. // 判断如果样本是空闲状态,设置为EVICTION状态
  863. // 如果不是,说明另一个线程已经借出了这个样本
  864. if (!underTest.startEvictionTest()) {
  865. i--;
  866. continue;
  867. }
  868. boolean evict;
  869. try {
  870. // 调用回收策略来判断是否回收该样本,按照默认策略,以下情况都会返回true:
  871. // 1. 样本空闲时间大于我们设置的idleSoftEvictTime,且当前池中空闲连接数量>minIdle
  872. // 2. 样本空闲时间大于我们设置的idleEvictTime
  873. evict = evictionPolicy.evict(evictionConfig, underTest,
  874. idleObjects.size());
  875. } catch (final Throwable t) {
  876. PoolUtils.checkRethrow(t);
  877. swallowException(new Exception(t));
  878. evict = false;
  879. }
  880. // 如果需要回收,则释放这个样本
  881. if (evict) {
  882. destroy(underTest);
  883. destroyedByEvictorCount.incrementAndGet();
  884. } else {
  885. // 如果设置了testWhileIdle,会
  886. if (testWhileIdle) {
  887. boolean active = false;
  888. try {
  889. // 利用工厂重新初始化样本,这里会校验maxConnLifetimeMillis
  890. factory.activateObject(underTest);
  891. active = true;
  892. } catch (final Exception e) {
  893. // 抛出异常标识校验不通过,释放样本
  894. destroy(underTest);
  895. destroyedByEvictorCount.incrementAndGet();
  896. }
  897. if (active) {
  898. // 接下来会校验maxConnLifetimeMillis和validationQueryTimeout
  899. if (!factory.validateObject(underTest)) {
  900. destroy(underTest);
  901. destroyedByEvictorCount.incrementAndGet();
  902. } else {
  903. try {
  904. // 这里会将样本rollbackOnReturn、autoCommitOnReturn等
  905. factory.passivateObject(underTest);
  906. } catch (final Exception e) {
  907. destroy(underTest);
  908. destroyedByEvictorCount.incrementAndGet();
  909. }
  910. }
  911. }
  912. }
  913. // 如果状态为EVICTION或EVICTION_RETURN_TO_HEAD,修改为IDLE
  914. if (!underTest.endEvictionTest(idleObjects)) {
  915. //空
  916. }
  917. }
  918. }
  919. }
  920. }
  921. // 校验所有连接的未使用时间是否超过removeAbandonedTimeout
  922. final AbandonedConfig ac = this.abandonedConfig;
  923. if (ac != null && ac.getRemoveAbandonedOnMaintenance()) {
  924. removeAbandoned(ac);
  925. }
  926. }
  927. ```
  928. 以上已基本研究完数据源创建、连接对象获取和空闲资源回收器,后续有空再做补充。
  929. # 通过JNDI获取数据源对象
  930. ## 需求
  931. 本文测试使用`JNDI`获取`PerUserPoolDataSource`和`SharedPoolDataSource`对象,选择使用`tomcat 9.0.21`作容器。
  932. 如果之前没有接触过`JNDI`,并不会影响下面例子的理解,其实可以理解为像`spring`的`bean`配置和获取。
  933. 源码分析时已经讲到,除了我们熟知的`BasicDataSource`,`DBCP`还提供了通过`JDNI`获取数据源,如下表。
  934. | 类名 | 描述 |
  935. | ----------------------- | ------------------------------------------------------------ |
  936. | `InstanceKeyDataSource` | 用于支持`JDNI`环境的数据源,是以下两个类的父类 |
  937. | `PerUserPoolDataSource` | `InstanceKeyDataSource`的子类,针对每个用户会单独分配一个连接池,每个连接池可以设置不同属性。例如以下需求,相比user,`admin`可以创建更多地连接以保证 |
  938. | `SharedPoolDataSource` | `InstanceKeyDataSource`的子类,不同用户共享一个连接池 |
  939. ## 引入依赖
  940. 本文在前面例子的基础上增加以下依赖,因为是web项目,所以打包方式为`war`:
  941. ```xml
  942. <dependency>
  943. <groupId>javax.servlet</groupId>
  944. <artifactId>jstl</artifactId>
  945. <version>1.2</version>
  946. <scope>provided</scope>
  947. </dependency>
  948. <dependency>
  949. <groupId>javax.servlet</groupId>
  950. <artifactId>javax.servlet-api</artifactId>
  951. <version>3.1.0</version>
  952. <scope>provided</scope>
  953. </dependency>
  954. <dependency>
  955. <groupId>javax.servlet.jsp</groupId>
  956. <artifactId>javax.servlet.jsp-api</artifactId>
  957. <version>2.2.1</version>
  958. <scope>provided</scope>
  959. </dependency>
  960. ```
  961. ## 编写context.xml
  962. 在`webapp`文件下创建目录`META-INF`,并创建`context.xml`文件。这里面的每个`resource`节点都是我们配置的对象,类似于`spring`的`bean`节点。其中`bean/DriverAdapterCPDS`这个对象需要被另外两个使用到。
  963. ```xml
  964. <?xml version="1.0" encoding="UTF-8"?>
  965. <Context>
  966. <Resource
  967. name="bean/SharedPoolDataSourceFactory"
  968. auth="Container"
  969. type="org.apache.commons.dbcp2.datasources.SharedPoolDataSource"
  970. factory="org.apache.commons.dbcp2.datasources.SharedPoolDataSourceFactory"
  971. singleton="false"
  972. driverClassName="com.mysql.cj.jdbc.Driver"
  973. url="jdbc:mysql://localhost:3306/github_demo?useUnicode=true&amp;characterEncoding=utf8&amp;serverTimezone=GMT%2B8&amp;useSSL=true"
  974. username="root"
  975. password="root"
  976. maxTotal="8"
  977. maxIdle="10"
  978. dataSourceName="java:comp/env/bean/DriverAdapterCPDS"
  979. />
  980. <Resource
  981. name="bean/PerUserPoolDataSourceFactory"
  982. auth="Container"
  983. type="org.apache.commons.dbcp2.datasources.PerUserPoolDataSource"
  984. factory="org.apache.commons.dbcp2.datasources.PerUserPoolDataSourceFactory"
  985. singleton="false"
  986. driverClassName="com.mysql.cj.jdbc.Driver"
  987. url="jdbc:mysql://localhost:3306/github_demo?useUnicode=true&amp;characterEncoding=utf8&amp;serverTimezone=GMT%2B8&amp;useSSL=true"
  988. username="root"
  989. password="root"
  990. maxTotal="8"
  991. maxIdle="10"
  992. dataSourceName="java:comp/env/bean/DriverAdapterCPDS"
  993. />
  994. <Resource
  995. name="bean/DriverAdapterCPDS"
  996. auth="Container"
  997. type="org.apache.commons.dbcp2.cpdsadapter.DriverAdapterCPDS"
  998. factory="org.apache.commons.dbcp2.cpdsadapter.DriverAdapterCPDS"
  999. singleton="false"
  1000. driverClassName="com.mysql.cj.jdbc.Driver"
  1001. url="jdbc:mysql://localhost:3306/github_demo?useUnicode=true&amp;characterEncoding=utf8&amp;serverTimezone=GMT%2B8&amp;useSSL=true"
  1002. userName="root"
  1003. userPassword="root"
  1004. maxIdle="10"
  1005. />
  1006. </Context>
  1007. ```
  1008. ## 编写web.xml
  1009. 在`web-app`节点下配置资源引用,每个`resource-env-ref`指向了我们配置好的对象。
  1010. ```xml
  1011. <resource-env-ref>
  1012. <description>Test DriverAdapterCPDS</description>
  1013. <resource-env-ref-name>bean/DriverAdapterCPDS</resource-env-ref-name>
  1014. <resource-env-ref-type>org.apache.commons.dbcp2.cpdsadapter.DriverAdapterCPDS</resource-env-ref-type>
  1015. </resource-env-ref>
  1016. <resource-env-ref>
  1017. <description>Test SharedPoolDataSource</description>
  1018. <resource-env-ref-name>bean/SharedPoolDataSourceFactory</resource-env-ref-name>
  1019. <resource-env-ref-type>org.apache.commons.dbcp2.datasources.SharedPoolDataSource</resource-env-ref-type>
  1020. </resource-env-ref>
  1021. <resource-env-ref>
  1022. <description>Test erUserPoolDataSource</description>
  1023. <resource-env-ref-name>bean/erUserPoolDataSourceFactory</resource-env-ref-name>
  1024. <resource-env-ref-type>org.apache.commons.dbcp2.datasources.erUserPoolDataSource</resource-env-ref-type>
  1025. </resource-env-ref>
  1026. ```
  1027. ## 编写jsp
  1028. 因为需要在`web`环境中使用,如果直接建类写个`main`方法测试,会一直报错的,目前没找到好的办法。这里就简单地使用`jsp`来测试吧(这是从tomcat官网参照的例子)。
  1029. ```jsp
  1030. <body>
  1031. <%
  1032. // 获得名称服务的上下文对象
  1033. Context initCtx = new InitialContext();
  1034. Context envCtx = (Context)initCtx.lookup("java:comp/env/");
  1035. // 查找指定名字的对象
  1036. DataSource ds = (DataSource)envCtx.lookup("bean/SharedPoolDataSourceFactory");
  1037. DataSource ds2 = (DataSource)envCtx.lookup("bean/PerUserPoolDataSourceFactory");
  1038. // 获取连接
  1039. Connection conn = ds.getConnection("root","root");
  1040. System.out.println("conn" + conn);
  1041. Connection conn2 = ds2.getConnection("zzf","zzf");
  1042. System.out.println("conn2" + conn2);
  1043. // ... 使用连接操作数据库,以及释放资源 ...
  1044. conn.close();
  1045. conn2.close();
  1046. %>
  1047. </body>
  1048. ```
  1049. ## 测试结果
  1050. 打包项目在`tomcat9`上运行,访问 http://localhost:8080/DBCP-demo/testInstanceKeyDataSource.jsp ,控制台打印如下内容:
  1051. ```
  1052. conn=1971654708, URL=jdbc:mysql://localhost:3306/github_demo?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&useSSL=true, UserName=root@localhost, MySQL Connector/J
  1053. conn2=128868782, URL=jdbc:mysql://localhost:3306/github_demo?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&useSSL=true, UserName=zzf@localhost, MySQL Connector/J
  1054. ```
  1055. # 使用DBCP测试两阶段提交
  1056. 前面源码分析已经讲到,以下类用于支持`JTA`事务。本文将介绍如何使用`DBCP`来实现`JTA`事务两阶段提交(当然,实际项目并不支持使用`2PC`,因为性能开销太大)。
  1057. | 类名 | 描述 |
  1058. | ------------------------ | ------------------------------------------------------------ |
  1059. | `BasicManagedDataSource` | `BasicDataSource`的子类,用于创建支持`XA`事务或`JTA`事务的连接 |
  1060. | `ManagedDataSource` | `PoolingDataSource`的子类,用于支持`XA`事务或`JTA`事务的连接。是`BasicManagedDataSource`中实际调用的数据源,可以说`BasicManagedDataSource`只是封装了`ManagedDataSource` |
  1061. ## 准备工作
  1062. 因为测试例子使用的是`mysql`,使用`XA`事务需要开启支持。注意,`mysql`只有`innoDB`引擎才支持(另外,`XA`事务和常规事务是互斥的,如果开启了`XA`事务,其他线程进来即使只读也是不行的)。
  1063. ```sql
  1064. SHOW VARIABLES LIKE '%xa%' -- 查看XA事务是否开启
  1065. SET innodb_support_xa = ON -- 开启XA事务
  1066. ```
  1067. 除了原来的`github_demo`数据库,我另外建了一个`test`数据库,简单地模拟两个数据库。
  1068. ## mysql的XA事务使用
  1069. 测试之前,这里简单回顾下直接使用`sql`操作`XA`事务的过程,将有助于对以下内容的理解:
  1070. ```sql
  1071. XA START 'my_test_xa'; -- 启动一个xid为my_test_xa的事务,并使之为active状态
  1072. UPDATE github_demo.demo_user SET deleted = 1 WHERE id = '1'; -- 事务中的语句
  1073. XA END 'my_test_xa'; -- 把事务置为idle状态
  1074. XA PREPARE 'my_test_xa'; -- 把事务置为prepare状态
  1075. XA COMMIT 'my_test_xa'; -- 提交事务
  1076. XA ROLLBACK 'my_test_xa'; -- 回滚事务
  1077. XA RECOVER; -- 查看处于prepare状态的事务列表
  1078. ```
  1079. ## 引入依赖
  1080. 在入门例子的基础上,增加以下依赖,本文采用第三方`atomikos`的实现。
  1081. ```xml
  1082. <!-- jta:用于测试DBCP对JTA事务的支持 -->
  1083. <dependency>
  1084. <groupId>javax.transaction</groupId>
  1085. <artifactId>jta</artifactId>
  1086. <version>1.1</version>
  1087. </dependency>
  1088. <dependency>
  1089. <groupId>com.atomikos</groupId>
  1090. <artifactId>transactions-jdbc</artifactId>
  1091. <version>3.9.3</version>
  1092. </dependency>
  1093. ```
  1094. ## 获取BasicManagedDataSource
  1095. 这里千万记得要设置`DefaultCatalog`,否则当前事务中注册不同资源管理器时,可能都会被当成同一个资源管理器而拒绝注册并报错,因为这个问题,花了我好长时间才解决。
  1096. ```java
  1097. public BasicManagedDataSource getBasicManagedDataSource(
  1098. TransactionManager transactionManager,
  1099. String url,
  1100. String username,
  1101. String password) {
  1102. BasicManagedDataSource basicManagedDataSource = new BasicManagedDataSource();
  1103. basicManagedDataSource.setTransactionManager(transactionManager);
  1104. basicManagedDataSource.setUrl(url);
  1105. basicManagedDataSource.setUsername(username);
  1106. basicManagedDataSource.setPassword(password);
  1107. basicManagedDataSource.setDefaultAutoCommit(false);
  1108. basicManagedDataSource.setXADataSource("com.mysql.cj.jdbc.MysqlXADataSource");
  1109. return basicManagedDataSource;
  1110. }
  1111. @Test
  1112. public void test01() throws Exception {
  1113. // 获得事务管理器
  1114. TransactionManager transactionManager = new UserTransactionManager();
  1115. // 获取第一个数据库的数据源
  1116. BasicManagedDataSource basicManagedDataSource1 = getBasicManagedDataSource(
  1117. transactionManager,
  1118. "jdbc:mysql://localhost:3306/github_demo?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&useSSL=true",
  1119. "root",
  1120. "root");
  1121. // 注意,这一步非常重要
  1122. basicManagedDataSource1.setDefaultCatalog("github_demo");
  1123. // 获取第二个数据库的数据源
  1124. BasicManagedDataSource basicManagedDataSource2 = getBasicManagedDataSource(
  1125. transactionManager,
  1126. "jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&useSSL=true",
  1127. "zzf",
  1128. "zzf");
  1129. // 注意,这一步非常重要
  1130. basicManagedDataSource1.setDefaultCatalog("test");
  1131. }
  1132. ```
  1133. ## 编写两阶段提交的代码
  1134. 通过运行代码可以发现,当数据库1和2的操作都成功,才会提交,只要其中一个数据库执行失败,两个操作都会回滚。
  1135. ```java
  1136. @Test
  1137. public void test01() throws Exception {
  1138. Connection connection1 = null;
  1139. Statement statement1 = null;
  1140. Connection connection2 = null;
  1141. Statement statement2 = null;
  1142. transactionManager.begin();
  1143. try {
  1144. // 获取连接并进行数据库操作,这里会将会将XAResource注册到当前线程的XA事务对象
  1145. /**
  1146. * XA START xid1;-- 启动一个事务,并使之为active状态
  1147. */
  1148. connection1 = basicManagedDataSource1.getConnection();
  1149. statement1 = connection1.createStatement();
  1150. /**
  1151. * update github_demo.demo_user set deleted = 1 where id = '1'; -- 事务中的语句
  1152. */
  1153. boolean result1 = statement1.execute("update github_demo.demo_user set deleted = 1 where id = '1'");
  1154. System.out.println(result1);
  1155. /**
  1156. * XA START xid2;-- 启动一个事务,并使之为active状态
  1157. */
  1158. connection2 = basicManagedDataSource2.getConnection();
  1159. statement2 = connection2.createStatement();
  1160. /**
  1161. * update test.demo_user set deleted = 1 where id = '1'; -- 事务中的语句
  1162. */
  1163. boolean result2 = statement2.execute("update test.demo_user set deleted = 1 where id = '1'");
  1164. System.out.println(result2);
  1165. /**
  1166. * 当这执行以下语句:
  1167. * XA END xid1; -- 把事务置为idle状态
  1168. * XA PREPARE xid1; -- 把事务置为prepare状态
  1169. * XA END xid2; -- 把事务置为idle状态
  1170. * XA PREPARE xid2; -- 把事务置为prepare状态
  1171. * XA COMMIT xid1; -- 提交事务
  1172. * XA COMMIT xid2; -- 提交事务
  1173. */
  1174. transactionManager.commit();
  1175. } catch(Exception e) {
  1176. e.printStackTrace();
  1177. } finally {
  1178. statement1.close();
  1179. statement2.close();
  1180. connection1.close();
  1181. connection2.close();
  1182. }
  1183. }
  1184. ```
  1185. > 本文为原创文章,转载请附上原文出处链接:https://github.com/ZhangZiSheng001/dbcp-demo。