1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.apache.ibatis.datasource.pooled;
17
18 import java.io.PrintWriter;
19 import java.lang.reflect.InvocationHandler;
20 import java.lang.reflect.Proxy;
21 import java.sql.Connection;
22 import java.sql.DriverManager;
23 import java.sql.SQLException;
24 import java.sql.Statement;
25 import java.util.Properties;
26 import java.util.logging.Logger;
27
28 import javax.sql.DataSource;
29
30 import org.apache.ibatis.datasource.unpooled.UnpooledDataSource;
31 import org.apache.ibatis.logging.Log;
32 import org.apache.ibatis.logging.LogFactory;
33
34
35
36
37
38
39 public class PooledDataSource implements DataSource {
40
41 private static final Log log = LogFactory.getLog(PooledDataSource.class);
42
43 private final PoolStatee/pooled/PoolState.html#PoolState">PoolState state = new PoolState(this);
44
45 private final UnpooledDataSource dataSource;
46
47
48 protected int poolMaximumActiveConnections = 10;
49 protected int poolMaximumIdleConnections = 5;
50 protected int poolMaximumCheckoutTime = 20000;
51 protected int poolTimeToWait = 20000;
52 protected int poolMaximumLocalBadConnectionTolerance = 3;
53 protected String poolPingQuery = "NO PING QUERY SET";
54 protected boolean poolPingEnabled;
55 protected int poolPingConnectionsNotUsedFor;
56
57 private int expectedConnectionTypeCode;
58
59 public PooledDataSource() {
60 dataSource = new UnpooledDataSource();
61 }
62
63 public PooledDataSource(UnpooledDataSource dataSource) {
64 this.dataSource = dataSource;
65 }
66
67 public PooledDataSource(String driver, String url, String username, String password) {
68 dataSource = new UnpooledDataSource(driver, url, username, password);
69 expectedConnectionTypeCode = assembleConnectionTypeCode(dataSource.getUrl(), dataSource.getUsername(), dataSource.getPassword());
70 }
71
72 public PooledDataSource(String driver, String url, Properties driverProperties) {
73 dataSource = new UnpooledDataSource(driver, url, driverProperties);
74 expectedConnectionTypeCode = assembleConnectionTypeCode(dataSource.getUrl(), dataSource.getUsername(), dataSource.getPassword());
75 }
76
77 public PooledDataSource(ClassLoader driverClassLoader, String driver, String url, String username, String password) {
78 dataSource = new UnpooledDataSource(driverClassLoader, driver, url, username, password);
79 expectedConnectionTypeCode = assembleConnectionTypeCode(dataSource.getUrl(), dataSource.getUsername(), dataSource.getPassword());
80 }
81
82 public PooledDataSource(ClassLoader driverClassLoader, String driver, String url, Properties driverProperties) {
83 dataSource = new UnpooledDataSource(driverClassLoader, driver, url, driverProperties);
84 expectedConnectionTypeCode = assembleConnectionTypeCode(dataSource.getUrl(), dataSource.getUsername(), dataSource.getPassword());
85 }
86
87 @Override
88 public Connection getConnection() throws SQLException {
89 return popConnection(dataSource.getUsername(), dataSource.getPassword()).getProxyConnection();
90 }
91
92 @Override
93 public Connection getConnection(String username, String password) throws SQLException {
94 return popConnection(username, password).getProxyConnection();
95 }
96
97 @Override
98 public void setLoginTimeout(int loginTimeout) {
99 DriverManager.setLoginTimeout(loginTimeout);
100 }
101
102 @Override
103 public int getLoginTimeout() {
104 return DriverManager.getLoginTimeout();
105 }
106
107 @Override
108 public void setLogWriter(PrintWriter logWriter) {
109 DriverManager.setLogWriter(logWriter);
110 }
111
112 @Override
113 public PrintWriter getLogWriter() {
114 return DriverManager.getLogWriter();
115 }
116
117 public void setDriver(String driver) {
118 dataSource.setDriver(driver);
119 forceCloseAll();
120 }
121
122 public void setUrl(String url) {
123 dataSource.setUrl(url);
124 forceCloseAll();
125 }
126
127 public void setUsername(String username) {
128 dataSource.setUsername(username);
129 forceCloseAll();
130 }
131
132 public void setPassword(String password) {
133 dataSource.setPassword(password);
134 forceCloseAll();
135 }
136
137 public void setDefaultAutoCommit(boolean defaultAutoCommit) {
138 dataSource.setAutoCommit(defaultAutoCommit);
139 forceCloseAll();
140 }
141
142 public void setDefaultTransactionIsolationLevel(Integer defaultTransactionIsolationLevel) {
143 dataSource.setDefaultTransactionIsolationLevel(defaultTransactionIsolationLevel);
144 forceCloseAll();
145 }
146
147 public void setDriverProperties(Properties driverProps) {
148 dataSource.setDriverProperties(driverProps);
149 forceCloseAll();
150 }
151
152
153
154
155
156
157
158
159 public void setDefaultNetworkTimeout(Integer milliseconds) {
160 dataSource.setDefaultNetworkTimeout(milliseconds);
161 forceCloseAll();
162 }
163
164
165
166
167
168
169 public void setPoolMaximumActiveConnections(int poolMaximumActiveConnections) {
170 this.poolMaximumActiveConnections = poolMaximumActiveConnections;
171 forceCloseAll();
172 }
173
174
175
176
177
178
179 public void setPoolMaximumIdleConnections(int poolMaximumIdleConnections) {
180 this.poolMaximumIdleConnections = poolMaximumIdleConnections;
181 forceCloseAll();
182 }
183
184
185
186
187
188
189
190
191
192
193 public void setPoolMaximumLocalBadConnectionTolerance(
194 int poolMaximumLocalBadConnectionTolerance) {
195 this.poolMaximumLocalBadConnectionTolerance = poolMaximumLocalBadConnectionTolerance;
196 }
197
198
199
200
201
202
203
204 public void setPoolMaximumCheckoutTime(int poolMaximumCheckoutTime) {
205 this.poolMaximumCheckoutTime = poolMaximumCheckoutTime;
206 forceCloseAll();
207 }
208
209
210
211
212
213
214 public void setPoolTimeToWait(int poolTimeToWait) {
215 this.poolTimeToWait = poolTimeToWait;
216 forceCloseAll();
217 }
218
219
220
221
222
223
224 public void setPoolPingQuery(String poolPingQuery) {
225 this.poolPingQuery = poolPingQuery;
226 forceCloseAll();
227 }
228
229
230
231
232
233
234 public void setPoolPingEnabled(boolean poolPingEnabled) {
235 this.poolPingEnabled = poolPingEnabled;
236 forceCloseAll();
237 }
238
239
240
241
242
243
244
245 public void setPoolPingConnectionsNotUsedFor(int milliseconds) {
246 this.poolPingConnectionsNotUsedFor = milliseconds;
247 forceCloseAll();
248 }
249
250 public String getDriver() {
251 return dataSource.getDriver();
252 }
253
254 public String getUrl() {
255 return dataSource.getUrl();
256 }
257
258 public String getUsername() {
259 return dataSource.getUsername();
260 }
261
262 public String getPassword() {
263 return dataSource.getPassword();
264 }
265
266 public boolean isAutoCommit() {
267 return dataSource.isAutoCommit();
268 }
269
270 public Integer getDefaultTransactionIsolationLevel() {
271 return dataSource.getDefaultTransactionIsolationLevel();
272 }
273
274 public Properties getDriverProperties() {
275 return dataSource.getDriverProperties();
276 }
277
278
279
280
281 public Integer getDefaultNetworkTimeout() {
282 return dataSource.getDefaultNetworkTimeout();
283 }
284
285 public int getPoolMaximumActiveConnections() {
286 return poolMaximumActiveConnections;
287 }
288
289 public int getPoolMaximumIdleConnections() {
290 return poolMaximumIdleConnections;
291 }
292
293 public int getPoolMaximumLocalBadConnectionTolerance() {
294 return poolMaximumLocalBadConnectionTolerance;
295 }
296
297 public int getPoolMaximumCheckoutTime() {
298 return poolMaximumCheckoutTime;
299 }
300
301 public int getPoolTimeToWait() {
302 return poolTimeToWait;
303 }
304
305 public String getPoolPingQuery() {
306 return poolPingQuery;
307 }
308
309 public boolean isPoolPingEnabled() {
310 return poolPingEnabled;
311 }
312
313 public int getPoolPingConnectionsNotUsedFor() {
314 return poolPingConnectionsNotUsedFor;
315 }
316
317
318
319
320 public void forceCloseAll() {
321 synchronized (state) {
322 expectedConnectionTypeCode = assembleConnectionTypeCode(dataSource.getUrl(), dataSource.getUsername(), dataSource.getPassword());
323 for (int i = state.activeConnections.size(); i > 0; i--) {
324 try {
325 PooledConnection conn = state.activeConnections.remove(i - 1);
326 conn.invalidate();
327
328 Connection realConn = conn.getRealConnection();
329 if (!realConn.getAutoCommit()) {
330 realConn.rollback();
331 }
332 realConn.close();
333 } catch (Exception e) {
334
335 }
336 }
337 for (int i = state.idleConnections.size(); i > 0; i--) {
338 try {
339 PooledConnection conn = state.idleConnections.remove(i - 1);
340 conn.invalidate();
341
342 Connection realConn = conn.getRealConnection();
343 if (!realConn.getAutoCommit()) {
344 realConn.rollback();
345 }
346 realConn.close();
347 } catch (Exception e) {
348
349 }
350 }
351 }
352 if (log.isDebugEnabled()) {
353 log.debug("PooledDataSource forcefully closed/removed all connections.");
354 }
355 }
356
357 public PoolState getPoolState() {
358 return state;
359 }
360
361 private int assembleConnectionTypeCode(String url, String username, String password) {
362 return ("" + url + username + password).hashCode();
363 }
364
365 protected void pushConnection(PooledConnection conn) throws SQLException {
366
367 synchronized (state) {
368 state.activeConnections.remove(conn);
369 if (conn.isValid()) {
370 if (state.idleConnections.size() < poolMaximumIdleConnections && conn.getConnectionTypeCode() == expectedConnectionTypeCode) {
371 state.accumulatedCheckoutTime += conn.getCheckoutTime();
372 if (!conn.getRealConnection().getAutoCommit()) {
373 conn.getRealConnection().rollback();
374 }
375 PooledConnectionPooledConnection.html#PooledConnection">PooledConnection newConn = new PooledConnection(conn.getRealConnection(), this);
376 state.idleConnections.add(newConn);
377 newConn.setCreatedTimestamp(conn.getCreatedTimestamp());
378 newConn.setLastUsedTimestamp(conn.getLastUsedTimestamp());
379 conn.invalidate();
380 if (log.isDebugEnabled()) {
381 log.debug("Returned connection " + newConn.getRealHashCode() + " to pool.");
382 }
383 state.notifyAll();
384 } else {
385 state.accumulatedCheckoutTime += conn.getCheckoutTime();
386 if (!conn.getRealConnection().getAutoCommit()) {
387 conn.getRealConnection().rollback();
388 }
389 conn.getRealConnection().close();
390 if (log.isDebugEnabled()) {
391 log.debug("Closed connection " + conn.getRealHashCode() + ".");
392 }
393 conn.invalidate();
394 }
395 } else {
396 if (log.isDebugEnabled()) {
397 log.debug("A bad connection (" + conn.getRealHashCode() + ") attempted to return to the pool, discarding connection.");
398 }
399 state.badConnectionCount++;
400 }
401 }
402 }
403
404 private PooledConnection popConnection(String username, String password) throws SQLException {
405 boolean countedWait = false;
406 PooledConnection conn = null;
407 long t = System.currentTimeMillis();
408 int localBadConnectionCount = 0;
409
410 while (conn == null) {
411 synchronized (state) {
412 if (!state.idleConnections.isEmpty()) {
413
414 conn = state.idleConnections.remove(0);
415 if (log.isDebugEnabled()) {
416 log.debug("Checked out connection " + conn.getRealHashCode() + " from pool.");
417 }
418 } else {
419
420 if (state.activeConnections.size() < poolMaximumActiveConnections) {
421
422 conn = new PooledConnection(dataSource.getConnection(), this);
423 if (log.isDebugEnabled()) {
424 log.debug("Created connection " + conn.getRealHashCode() + ".");
425 }
426 } else {
427
428 PooledConnection oldestActiveConnection = state.activeConnections.get(0);
429 long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();
430 if (longestCheckoutTime > poolMaximumCheckoutTime) {
431
432 state.claimedOverdueConnectionCount++;
433 state.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime;
434 state.accumulatedCheckoutTime += longestCheckoutTime;
435 state.activeConnections.remove(oldestActiveConnection);
436 if (!oldestActiveConnection.getRealConnection().getAutoCommit()) {
437 try {
438 oldestActiveConnection.getRealConnection().rollback();
439 } catch (SQLException e) {
440
441
442
443
444
445
446
447
448 log.debug("Bad connection. Could not roll back");
449 }
450 }
451 conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this);
452 conn.setCreatedTimestamp(oldestActiveConnection.getCreatedTimestamp());
453 conn.setLastUsedTimestamp(oldestActiveConnection.getLastUsedTimestamp());
454 oldestActiveConnection.invalidate();
455 if (log.isDebugEnabled()) {
456 log.debug("Claimed overdue connection " + conn.getRealHashCode() + ".");
457 }
458 } else {
459
460 try {
461 if (!countedWait) {
462 state.hadToWaitCount++;
463 countedWait = true;
464 }
465 if (log.isDebugEnabled()) {
466 log.debug("Waiting as long as " + poolTimeToWait + " milliseconds for connection.");
467 }
468 long wt = System.currentTimeMillis();
469 state.wait(poolTimeToWait);
470 state.accumulatedWaitTime += System.currentTimeMillis() - wt;
471 } catch (InterruptedException e) {
472 break;
473 }
474 }
475 }
476 }
477 if (conn != null) {
478
479 if (conn.isValid()) {
480 if (!conn.getRealConnection().getAutoCommit()) {
481 conn.getRealConnection().rollback();
482 }
483 conn.setConnectionTypeCode(assembleConnectionTypeCode(dataSource.getUrl(), username, password));
484 conn.setCheckoutTimestamp(System.currentTimeMillis());
485 conn.setLastUsedTimestamp(System.currentTimeMillis());
486 state.activeConnections.add(conn);
487 state.requestCount++;
488 state.accumulatedRequestTime += System.currentTimeMillis() - t;
489 } else {
490 if (log.isDebugEnabled()) {
491 log.debug("A bad connection (" + conn.getRealHashCode() + ") was returned from the pool, getting another connection.");
492 }
493 state.badConnectionCount++;
494 localBadConnectionCount++;
495 conn = null;
496 if (localBadConnectionCount > (poolMaximumIdleConnections + poolMaximumLocalBadConnectionTolerance)) {
497 if (log.isDebugEnabled()) {
498 log.debug("PooledDataSource: Could not get a good connection to the database.");
499 }
500 throw new SQLException("PooledDataSource: Could not get a good connection to the database.");
501 }
502 }
503 }
504 }
505
506 }
507
508 if (conn == null) {
509 if (log.isDebugEnabled()) {
510 log.debug("PooledDataSource: Unknown severe error condition. The connection pool returned a null connection.");
511 }
512 throw new SQLException("PooledDataSource: Unknown severe error condition. The connection pool returned a null connection.");
513 }
514
515 return conn;
516 }
517
518
519
520
521
522
523
524 protected boolean pingConnection(PooledConnection conn) {
525 boolean result = true;
526
527 try {
528 result = !conn.getRealConnection().isClosed();
529 } catch (SQLException e) {
530 if (log.isDebugEnabled()) {
531 log.debug("Connection " + conn.getRealHashCode() + " is BAD: " + e.getMessage());
532 }
533 result = false;
534 }
535
536 if (result) {
537 if (poolPingEnabled) {
538 if (poolPingConnectionsNotUsedFor >= 0 && conn.getTimeElapsedSinceLastUse() > poolPingConnectionsNotUsedFor) {
539 try {
540 if (log.isDebugEnabled()) {
541 log.debug("Testing connection " + conn.getRealHashCode() + " ...");
542 }
543 Connection realConn = conn.getRealConnection();
544 try (Statement statement = realConn.createStatement()) {
545 statement.executeQuery(poolPingQuery).close();
546 }
547 if (!realConn.getAutoCommit()) {
548 realConn.rollback();
549 }
550 result = true;
551 if (log.isDebugEnabled()) {
552 log.debug("Connection " + conn.getRealHashCode() + " is GOOD!");
553 }
554 } catch (Exception e) {
555 log.warn("Execution of ping query '" + poolPingQuery + "' failed: " + e.getMessage());
556 try {
557 conn.getRealConnection().close();
558 } catch (Exception e2) {
559
560 }
561 result = false;
562 if (log.isDebugEnabled()) {
563 log.debug("Connection " + conn.getRealHashCode() + " is BAD: " + e.getMessage());
564 }
565 }
566 }
567 }
568 }
569 return result;
570 }
571
572
573
574
575
576
577
578 public static Connection unwrapConnection(Connection conn) {
579 if (Proxy.isProxyClass(conn.getClass())) {
580 InvocationHandler handler = Proxy.getInvocationHandler(conn);
581 if (handler instanceof PooledConnection) {
582 return ((PooledConnection) handler).getRealConnection();
583 }
584 }
585 return conn;
586 }
587
588 @Override
589 protected void finalize() throws Throwable {
590 forceCloseAll();
591 super.finalize();
592 }
593
594 @Override
595 public <T> T unwrap(Class<T> iface) throws SQLException {
596 throw new SQLException(getClass().getName() + " is not a wrapper.");
597 }
598
599 @Override
600 public boolean isWrapperFor(Class<?> iface) {
601 return false;
602 }
603
604 @Override
605 public Logger getParentLogger() {
606 return Logger.getLogger(Logger.GLOBAL_LOGGER_NAME);
607 }
608
609 }