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.ResultSet;
24 import java.sql.SQLException;
25 import java.sql.Statement;
26 import java.util.Properties;
27 import java.util.logging.Logger;
28
29 import javax.sql.DataSource;
30
31 import org.apache.ibatis.datasource.unpooled.UnpooledDataSource;
32 import org.apache.ibatis.logging.Log;
33 import org.apache.ibatis.logging.LogFactory;
34
35
36
37
38
39
40 public class PooledDataSource implements DataSource {
41
42 private static final Log log = LogFactory.getLog(PooledDataSource.class);
43
44 private final PoolState state = new PoolState(this);
45
46 private final UnpooledDataSource dataSource;
47
48
49 protected int poolMaximumActiveConnections = 10;
50 protected int poolMaximumIdleConnections = 5;
51 protected int poolMaximumCheckoutTime = 20000;
52 protected int poolTimeToWait = 20000;
53 protected String poolPingQuery = "NO PING QUERY SET";
54 protected boolean poolPingEnabled = false;
55 protected int poolPingConnectionsNotUsedFor = 0;
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) throws SQLException {
99 DriverManager.setLoginTimeout(loginTimeout);
100 }
101
102 @Override
103 public int getLoginTimeout() throws SQLException {
104 return DriverManager.getLoginTimeout();
105 }
106
107 @Override
108 public void setLogWriter(PrintWriter logWriter) throws SQLException {
109 DriverManager.setLogWriter(logWriter);
110 }
111
112 @Override
113 public PrintWriter getLogWriter() throws SQLException {
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 public void setPoolMaximumActiveConnections(int poolMaximumActiveConnections) {
158 this.poolMaximumActiveConnections = poolMaximumActiveConnections;
159 forceCloseAll();
160 }
161
162
163
164
165
166
167 public void setPoolMaximumIdleConnections(int poolMaximumIdleConnections) {
168 this.poolMaximumIdleConnections = poolMaximumIdleConnections;
169 forceCloseAll();
170 }
171
172
173
174
175
176
177
178 public void setPoolMaximumCheckoutTime(int poolMaximumCheckoutTime) {
179 this.poolMaximumCheckoutTime = poolMaximumCheckoutTime;
180 forceCloseAll();
181 }
182
183
184
185
186
187
188 public void setPoolTimeToWait(int poolTimeToWait) {
189 this.poolTimeToWait = poolTimeToWait;
190 forceCloseAll();
191 }
192
193
194
195
196
197
198 public void setPoolPingQuery(String poolPingQuery) {
199 this.poolPingQuery = poolPingQuery;
200 forceCloseAll();
201 }
202
203
204
205
206
207
208 public void setPoolPingEnabled(boolean poolPingEnabled) {
209 this.poolPingEnabled = poolPingEnabled;
210 forceCloseAll();
211 }
212
213
214
215
216
217
218
219 public void setPoolPingConnectionsNotUsedFor(int milliseconds) {
220 this.poolPingConnectionsNotUsedFor = milliseconds;
221 forceCloseAll();
222 }
223
224 public String getDriver() {
225 return dataSource.getDriver();
226 }
227
228 public String getUrl() {
229 return dataSource.getUrl();
230 }
231
232 public String getUsername() {
233 return dataSource.getUsername();
234 }
235
236 public String getPassword() {
237 return dataSource.getPassword();
238 }
239
240 public boolean isAutoCommit() {
241 return dataSource.isAutoCommit();
242 }
243
244 public Integer getDefaultTransactionIsolationLevel() {
245 return dataSource.getDefaultTransactionIsolationLevel();
246 }
247
248 public Properties getDriverProperties() {
249 return dataSource.getDriverProperties();
250 }
251
252 public int getPoolMaximumActiveConnections() {
253 return poolMaximumActiveConnections;
254 }
255
256 public int getPoolMaximumIdleConnections() {
257 return poolMaximumIdleConnections;
258 }
259
260 public int getPoolMaximumCheckoutTime() {
261 return poolMaximumCheckoutTime;
262 }
263
264 public int getPoolTimeToWait() {
265 return poolTimeToWait;
266 }
267
268 public String getPoolPingQuery() {
269 return poolPingQuery;
270 }
271
272 public boolean isPoolPingEnabled() {
273 return poolPingEnabled;
274 }
275
276 public int getPoolPingConnectionsNotUsedFor() {
277 return poolPingConnectionsNotUsedFor;
278 }
279
280
281
282
283 public void forceCloseAll() {
284 synchronized (state) {
285 expectedConnectionTypeCode = assembleConnectionTypeCode(dataSource.getUrl(), dataSource.getUsername(), dataSource.getPassword());
286 for (int i = state.activeConnections.size(); i > 0; i--) {
287 try {
288 PooledConnection conn = state.activeConnections.remove(i - 1);
289 conn.invalidate();
290
291 Connection realConn = conn.getRealConnection();
292 if (!realConn.getAutoCommit()) {
293 realConn.rollback();
294 }
295 realConn.close();
296 } catch (Exception e) {
297
298 }
299 }
300 for (int i = state.idleConnections.size(); i > 0; i--) {
301 try {
302 PooledConnection conn = state.idleConnections.remove(i - 1);
303 conn.invalidate();
304
305 Connection realConn = conn.getRealConnection();
306 if (!realConn.getAutoCommit()) {
307 realConn.rollback();
308 }
309 realConn.close();
310 } catch (Exception e) {
311
312 }
313 }
314 }
315 if (log.isDebugEnabled()) {
316 log.debug("PooledDataSource forcefully closed/removed all connections.");
317 }
318 }
319
320 public PoolState getPoolState() {
321 return state;
322 }
323
324 private int assembleConnectionTypeCode(String url, String username, String password) {
325 return ("" + url + username + password).hashCode();
326 }
327
328 protected void pushConnection(PooledConnection conn) throws SQLException {
329
330 synchronized (state) {
331 state.activeConnections.remove(conn);
332 if (conn.isValid()) {
333 if (state.idleConnections.size() < poolMaximumIdleConnections && conn.getConnectionTypeCode() == expectedConnectionTypeCode) {
334 state.accumulatedCheckoutTime += conn.getCheckoutTime();
335 if (!conn.getRealConnection().getAutoCommit()) {
336 conn.getRealConnection().rollback();
337 }
338 PooledConnection newConn = new PooledConnection(conn.getRealConnection(), this);
339 state.idleConnections.add(newConn);
340 newConn.setCreatedTimestamp(conn.getCreatedTimestamp());
341 newConn.setLastUsedTimestamp(conn.getLastUsedTimestamp());
342 conn.invalidate();
343 if (log.isDebugEnabled()) {
344 log.debug("Returned connection " + newConn.getRealHashCode() + " to pool.");
345 }
346 state.notifyAll();
347 } else {
348 state.accumulatedCheckoutTime += conn.getCheckoutTime();
349 if (!conn.getRealConnection().getAutoCommit()) {
350 conn.getRealConnection().rollback();
351 }
352 conn.getRealConnection().close();
353 if (log.isDebugEnabled()) {
354 log.debug("Closed connection " + conn.getRealHashCode() + ".");
355 }
356 conn.invalidate();
357 }
358 } else {
359 if (log.isDebugEnabled()) {
360 log.debug("A bad connection (" + conn.getRealHashCode() + ") attempted to return to the pool, discarding connection.");
361 }
362 state.badConnectionCount++;
363 }
364 }
365 }
366
367 private PooledConnection popConnection(String username, String password) throws SQLException {
368 boolean countedWait = false;
369 PooledConnection conn = null;
370 long t = System.currentTimeMillis();
371 int localBadConnectionCount = 0;
372
373 while (conn == null) {
374 synchronized (state) {
375 if (!state.idleConnections.isEmpty()) {
376
377 conn = state.idleConnections.remove(0);
378 if (log.isDebugEnabled()) {
379 log.debug("Checked out connection " + conn.getRealHashCode() + " from pool.");
380 }
381 } else {
382
383 if (state.activeConnections.size() < poolMaximumActiveConnections) {
384
385 conn = new PooledConnection(dataSource.getConnection(), this);
386 if (log.isDebugEnabled()) {
387 log.debug("Created connection " + conn.getRealHashCode() + ".");
388 }
389 } else {
390
391 PooledConnection oldestActiveConnection = state.activeConnections.get(0);
392 long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();
393 if (longestCheckoutTime > poolMaximumCheckoutTime) {
394
395 state.claimedOverdueConnectionCount++;
396 state.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime;
397 state.accumulatedCheckoutTime += longestCheckoutTime;
398 state.activeConnections.remove(oldestActiveConnection);
399 if (!oldestActiveConnection.getRealConnection().getAutoCommit()) {
400 oldestActiveConnection.getRealConnection().rollback();
401 }
402 conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this);
403 oldestActiveConnection.invalidate();
404 if (log.isDebugEnabled()) {
405 log.debug("Claimed overdue connection " + conn.getRealHashCode() + ".");
406 }
407 } else {
408
409 try {
410 if (!countedWait) {
411 state.hadToWaitCount++;
412 countedWait = true;
413 }
414 if (log.isDebugEnabled()) {
415 log.debug("Waiting as long as " + poolTimeToWait + " milliseconds for connection.");
416 }
417 long wt = System.currentTimeMillis();
418 state.wait(poolTimeToWait);
419 state.accumulatedWaitTime += System.currentTimeMillis() - wt;
420 } catch (InterruptedException e) {
421 break;
422 }
423 }
424 }
425 }
426 if (conn != null) {
427 if (conn.isValid()) {
428 if (!conn.getRealConnection().getAutoCommit()) {
429 conn.getRealConnection().rollback();
430 }
431 conn.setConnectionTypeCode(assembleConnectionTypeCode(dataSource.getUrl(), username, password));
432 conn.setCheckoutTimestamp(System.currentTimeMillis());
433 conn.setLastUsedTimestamp(System.currentTimeMillis());
434 state.activeConnections.add(conn);
435 state.requestCount++;
436 state.accumulatedRequestTime += System.currentTimeMillis() - t;
437 } else {
438 if (log.isDebugEnabled()) {
439 log.debug("A bad connection (" + conn.getRealHashCode() + ") was returned from the pool, getting another connection.");
440 }
441 state.badConnectionCount++;
442 localBadConnectionCount++;
443 conn = null;
444 if (localBadConnectionCount > (poolMaximumIdleConnections + 3)) {
445 if (log.isDebugEnabled()) {
446 log.debug("PooledDataSource: Could not get a good connection to the database.");
447 }
448 throw new SQLException("PooledDataSource: Could not get a good connection to the database.");
449 }
450 }
451 }
452 }
453
454 }
455
456 if (conn == null) {
457 if (log.isDebugEnabled()) {
458 log.debug("PooledDataSource: Unknown severe error condition. The connection pool returned a null connection.");
459 }
460 throw new SQLException("PooledDataSource: Unknown severe error condition. The connection pool returned a null connection.");
461 }
462
463 return conn;
464 }
465
466
467
468
469
470
471
472 protected boolean pingConnection(PooledConnection conn) {
473 boolean result = true;
474
475 try {
476 result = !conn.getRealConnection().isClosed();
477 } catch (SQLException e) {
478 if (log.isDebugEnabled()) {
479 log.debug("Connection " + conn.getRealHashCode() + " is BAD: " + e.getMessage());
480 }
481 result = false;
482 }
483
484 if (result) {
485 if (poolPingEnabled) {
486 if (poolPingConnectionsNotUsedFor >= 0 && conn.getTimeElapsedSinceLastUse() > poolPingConnectionsNotUsedFor) {
487 try {
488 if (log.isDebugEnabled()) {
489 log.debug("Testing connection " + conn.getRealHashCode() + " ...");
490 }
491 Connection realConn = conn.getRealConnection();
492 Statement statement = realConn.createStatement();
493 ResultSet rs = statement.executeQuery(poolPingQuery);
494 rs.close();
495 statement.close();
496 if (!realConn.getAutoCommit()) {
497 realConn.rollback();
498 }
499 result = true;
500 if (log.isDebugEnabled()) {
501 log.debug("Connection " + conn.getRealHashCode() + " is GOOD!");
502 }
503 } catch (Exception e) {
504 log.warn("Execution of ping query '" + poolPingQuery + "' failed: " + e.getMessage());
505 try {
506 conn.getRealConnection().close();
507 } catch (Exception e2) {
508
509 }
510 result = false;
511 if (log.isDebugEnabled()) {
512 log.debug("Connection " + conn.getRealHashCode() + " is BAD: " + e.getMessage());
513 }
514 }
515 }
516 }
517 }
518 return result;
519 }
520
521
522
523
524
525
526
527 public static Connection unwrapConnection(Connection conn) {
528 if (Proxy.isProxyClass(conn.getClass())) {
529 InvocationHandler handler = Proxy.getInvocationHandler(conn);
530 if (handler instanceof PooledConnection) {
531 return ((PooledConnection) handler).getRealConnection();
532 }
533 }
534 return conn;
535 }
536
537 protected void finalize() throws Throwable {
538 forceCloseAll();
539 super.finalize();
540 }
541
542 public <T> T unwrap(Class<T> iface) throws SQLException {
543 throw new SQLException(getClass().getName() + " is not a wrapper.");
544 }
545
546 public boolean isWrapperFor(Class<?> iface) throws SQLException {
547 return false;
548 }
549
550 public Logger getParentLogger() {
551 return Logger.getLogger(Logger.GLOBAL_LOGGER_NAME);
552 }
553
554 }