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 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
170 public void setPoolMaximumActiveConnections(int poolMaximumActiveConnections) {
171 this.poolMaximumActiveConnections = poolMaximumActiveConnections;
172 forceCloseAll();
173 }
174
175
176
177
178
179
180
181 public void setPoolMaximumIdleConnections(int poolMaximumIdleConnections) {
182 this.poolMaximumIdleConnections = poolMaximumIdleConnections;
183 forceCloseAll();
184 }
185
186
187
188
189
190
191
192
193
194
195 public void setPoolMaximumLocalBadConnectionTolerance(
196 int poolMaximumLocalBadConnectionTolerance) {
197 this.poolMaximumLocalBadConnectionTolerance = poolMaximumLocalBadConnectionTolerance;
198 }
199
200
201
202
203
204
205
206
207 public void setPoolMaximumCheckoutTime(int poolMaximumCheckoutTime) {
208 this.poolMaximumCheckoutTime = poolMaximumCheckoutTime;
209 forceCloseAll();
210 }
211
212
213
214
215
216
217
218 public void setPoolTimeToWait(int poolTimeToWait) {
219 this.poolTimeToWait = poolTimeToWait;
220 forceCloseAll();
221 }
222
223
224
225
226
227
228
229 public void setPoolPingQuery(String poolPingQuery) {
230 this.poolPingQuery = poolPingQuery;
231 forceCloseAll();
232 }
233
234
235
236
237
238
239
240 public void setPoolPingEnabled(boolean poolPingEnabled) {
241 this.poolPingEnabled = poolPingEnabled;
242 forceCloseAll();
243 }
244
245
246
247
248
249
250
251
252 public void setPoolPingConnectionsNotUsedFor(int milliseconds) {
253 this.poolPingConnectionsNotUsedFor = milliseconds;
254 forceCloseAll();
255 }
256
257 public String getDriver() {
258 return dataSource.getDriver();
259 }
260
261 public String getUrl() {
262 return dataSource.getUrl();
263 }
264
265 public String getUsername() {
266 return dataSource.getUsername();
267 }
268
269 public String getPassword() {
270 return dataSource.getPassword();
271 }
272
273 public boolean isAutoCommit() {
274 return dataSource.isAutoCommit();
275 }
276
277 public Integer getDefaultTransactionIsolationLevel() {
278 return dataSource.getDefaultTransactionIsolationLevel();
279 }
280
281 public Properties getDriverProperties() {
282 return dataSource.getDriverProperties();
283 }
284
285
286
287
288
289
290
291 public Integer getDefaultNetworkTimeout() {
292 return dataSource.getDefaultNetworkTimeout();
293 }
294
295 public int getPoolMaximumActiveConnections() {
296 return poolMaximumActiveConnections;
297 }
298
299 public int getPoolMaximumIdleConnections() {
300 return poolMaximumIdleConnections;
301 }
302
303 public int getPoolMaximumLocalBadConnectionTolerance() {
304 return poolMaximumLocalBadConnectionTolerance;
305 }
306
307 public int getPoolMaximumCheckoutTime() {
308 return poolMaximumCheckoutTime;
309 }
310
311 public int getPoolTimeToWait() {
312 return poolTimeToWait;
313 }
314
315 public String getPoolPingQuery() {
316 return poolPingQuery;
317 }
318
319 public boolean isPoolPingEnabled() {
320 return poolPingEnabled;
321 }
322
323 public int getPoolPingConnectionsNotUsedFor() {
324 return poolPingConnectionsNotUsedFor;
325 }
326
327
328
329
330 public void forceCloseAll() {
331 synchronized (state) {
332 expectedConnectionTypeCode = assembleConnectionTypeCode(dataSource.getUrl(), dataSource.getUsername(), dataSource.getPassword());
333 for (int i = state.activeConnections.size(); i > 0; i--) {
334 try {
335 PooledConnection conn = state.activeConnections.remove(i - 1);
336 conn.invalidate();
337
338 Connection realConn = conn.getRealConnection();
339 if (!realConn.getAutoCommit()) {
340 realConn.rollback();
341 }
342 realConn.close();
343 } catch (Exception e) {
344
345 }
346 }
347 for (int i = state.idleConnections.size(); i > 0; i--) {
348 try {
349 PooledConnection conn = state.idleConnections.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 }
362 if (log.isDebugEnabled()) {
363 log.debug("PooledDataSource forcefully closed/removed all connections.");
364 }
365 }
366
367 public PoolState getPoolState() {
368 return state;
369 }
370
371 private int assembleConnectionTypeCode(String url, String username, String password) {
372 return ("" + url + username + password).hashCode();
373 }
374
375 protected void pushConnection(PooledConnection conn) throws SQLException {
376
377 synchronized (state) {
378 state.activeConnections.remove(conn);
379 if (conn.isValid()) {
380 if (state.idleConnections.size() < poolMaximumIdleConnections && conn.getConnectionTypeCode() == expectedConnectionTypeCode) {
381 state.accumulatedCheckoutTime += conn.getCheckoutTime();
382 if (!conn.getRealConnection().getAutoCommit()) {
383 conn.getRealConnection().rollback();
384 }
385 PooledConnection newConn = new PooledConnection(conn.getRealConnection(), this);
386 state.idleConnections.add(newConn);
387 newConn.setCreatedTimestamp(conn.getCreatedTimestamp());
388 newConn.setLastUsedTimestamp(conn.getLastUsedTimestamp());
389 conn.invalidate();
390 if (log.isDebugEnabled()) {
391 log.debug("Returned connection " + newConn.getRealHashCode() + " to pool.");
392 }
393 state.notifyAll();
394 } else {
395 state.accumulatedCheckoutTime += conn.getCheckoutTime();
396 if (!conn.getRealConnection().getAutoCommit()) {
397 conn.getRealConnection().rollback();
398 }
399 conn.getRealConnection().close();
400 if (log.isDebugEnabled()) {
401 log.debug("Closed connection " + conn.getRealHashCode() + ".");
402 }
403 conn.invalidate();
404 }
405 } else {
406 if (log.isDebugEnabled()) {
407 log.debug("A bad connection (" + conn.getRealHashCode() + ") attempted to return to the pool, discarding connection.");
408 }
409 state.badConnectionCount++;
410 }
411 }
412 }
413
414 private PooledConnection popConnection(String username, String password) throws SQLException {
415 boolean countedWait = false;
416 PooledConnection conn = null;
417 long t = System.currentTimeMillis();
418 int localBadConnectionCount = 0;
419
420 while (conn == null) {
421 synchronized (state) {
422 if (!state.idleConnections.isEmpty()) {
423
424 conn = state.idleConnections.remove(0);
425 if (log.isDebugEnabled()) {
426 log.debug("Checked out connection " + conn.getRealHashCode() + " from pool.");
427 }
428 } else {
429
430 if (state.activeConnections.size() < poolMaximumActiveConnections) {
431
432 conn = new PooledConnection(dataSource.getConnection(), this);
433 if (log.isDebugEnabled()) {
434 log.debug("Created connection " + conn.getRealHashCode() + ".");
435 }
436 } else {
437
438 PooledConnection oldestActiveConnection = state.activeConnections.get(0);
439 long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();
440 if (longestCheckoutTime > poolMaximumCheckoutTime) {
441
442 state.claimedOverdueConnectionCount++;
443 state.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime;
444 state.accumulatedCheckoutTime += longestCheckoutTime;
445 state.activeConnections.remove(oldestActiveConnection);
446 if (!oldestActiveConnection.getRealConnection().getAutoCommit()) {
447 try {
448 oldestActiveConnection.getRealConnection().rollback();
449 } catch (SQLException e) {
450
451
452
453
454
455
456
457
458 log.debug("Bad connection. Could not roll back");
459 }
460 }
461 conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this);
462 conn.setCreatedTimestamp(oldestActiveConnection.getCreatedTimestamp());
463 conn.setLastUsedTimestamp(oldestActiveConnection.getLastUsedTimestamp());
464 oldestActiveConnection.invalidate();
465 if (log.isDebugEnabled()) {
466 log.debug("Claimed overdue connection " + conn.getRealHashCode() + ".");
467 }
468 } else {
469
470 try {
471 if (!countedWait) {
472 state.hadToWaitCount++;
473 countedWait = true;
474 }
475 if (log.isDebugEnabled()) {
476 log.debug("Waiting as long as " + poolTimeToWait + " milliseconds for connection.");
477 }
478 long wt = System.currentTimeMillis();
479 state.wait(poolTimeToWait);
480 state.accumulatedWaitTime += System.currentTimeMillis() - wt;
481 } catch (InterruptedException e) {
482
483 Thread.currentThread().interrupt();
484 break;
485 }
486 }
487 }
488 }
489 if (conn != null) {
490
491 if (conn.isValid()) {
492 if (!conn.getRealConnection().getAutoCommit()) {
493 conn.getRealConnection().rollback();
494 }
495 conn.setConnectionTypeCode(assembleConnectionTypeCode(dataSource.getUrl(), username, password));
496 conn.setCheckoutTimestamp(System.currentTimeMillis());
497 conn.setLastUsedTimestamp(System.currentTimeMillis());
498 state.activeConnections.add(conn);
499 state.requestCount++;
500 state.accumulatedRequestTime += System.currentTimeMillis() - t;
501 } else {
502 if (log.isDebugEnabled()) {
503 log.debug("A bad connection (" + conn.getRealHashCode() + ") was returned from the pool, getting another connection.");
504 }
505 state.badConnectionCount++;
506 localBadConnectionCount++;
507 conn = null;
508 if (localBadConnectionCount > (poolMaximumIdleConnections + poolMaximumLocalBadConnectionTolerance)) {
509 if (log.isDebugEnabled()) {
510 log.debug("PooledDataSource: Could not get a good connection to the database.");
511 }
512 throw new SQLException("PooledDataSource: Could not get a good connection to the database.");
513 }
514 }
515 }
516 }
517
518 }
519
520 if (conn == null) {
521 if (log.isDebugEnabled()) {
522 log.debug("PooledDataSource: Unknown severe error condition. The connection pool returned a null connection.");
523 }
524 throw new SQLException("PooledDataSource: Unknown severe error condition. The connection pool returned a null connection.");
525 }
526
527 return conn;
528 }
529
530
531
532
533
534
535
536
537 protected boolean pingConnection(PooledConnection conn) {
538 boolean result = true;
539
540 try {
541 result = !conn.getRealConnection().isClosed();
542 } catch (SQLException e) {
543 if (log.isDebugEnabled()) {
544 log.debug("Connection " + conn.getRealHashCode() + " is BAD: " + e.getMessage());
545 }
546 result = false;
547 }
548
549 if (result && poolPingEnabled && poolPingConnectionsNotUsedFor >= 0
550 && conn.getTimeElapsedSinceLastUse() > poolPingConnectionsNotUsedFor) {
551 try {
552 if (log.isDebugEnabled()) {
553 log.debug("Testing connection " + conn.getRealHashCode() + " ...");
554 }
555 Connection realConn = conn.getRealConnection();
556 try (Statement statement = realConn.createStatement()) {
557 statement.executeQuery(poolPingQuery).close();
558 }
559 if (!realConn.getAutoCommit()) {
560 realConn.rollback();
561 }
562 result = true;
563 if (log.isDebugEnabled()) {
564 log.debug("Connection " + conn.getRealHashCode() + " is GOOD!");
565 }
566 } catch (Exception e) {
567 log.warn("Execution of ping query '" + poolPingQuery + "' failed: " + e.getMessage());
568 try {
569 conn.getRealConnection().close();
570 } catch (Exception e2) {
571
572 }
573 result = false;
574 if (log.isDebugEnabled()) {
575 log.debug("Connection " + conn.getRealHashCode() + " is BAD: " + e.getMessage());
576 }
577 }
578 }
579 return result;
580 }
581
582
583
584
585
586
587
588
589 public static Connection unwrapConnection(Connection conn) {
590 if (Proxy.isProxyClass(conn.getClass())) {
591 InvocationHandler handler = Proxy.getInvocationHandler(conn);
592 if (handler instanceof PooledConnection) {
593 return ((PooledConnection) handler).getRealConnection();
594 }
595 }
596 return conn;
597 }
598
599 @Override
600 protected void finalize() throws Throwable {
601 forceCloseAll();
602 super.finalize();
603 }
604
605 @Override
606 public <T> T unwrap(Class<T> iface) throws SQLException {
607 throw new SQLException(getClass().getName() + " is not a wrapper.");
608 }
609
610 @Override
611 public boolean isWrapperFor(Class<?> iface) {
612 return false;
613 }
614
615 @Override
616 public Logger getParentLogger() {
617 return Logger.getLogger(Logger.GLOBAL_LOGGER_NAME);
618 }
619
620 }