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