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