001package io.ebean.datasource.pool; 002 003import io.ebean.datasource.DataSourceAlert; 004import io.ebean.datasource.DataSourceConfig; 005import io.ebean.datasource.DataSourceConfigurationException; 006import io.ebean.datasource.DataSourceInitialiseException; 007import io.ebean.datasource.DataSourcePool; 008import io.ebean.datasource.DataSourcePoolListener; 009import io.ebean.datasource.InitDatabase; 010import io.ebean.datasource.PoolStatistics; 011import io.ebean.datasource.PoolStatus; 012import org.slf4j.Logger; 013import org.slf4j.LoggerFactory; 014 015import java.io.PrintWriter; 016import java.sql.Connection; 017import java.sql.DriverManager; 018import java.sql.ResultSet; 019import java.sql.SQLException; 020import java.sql.SQLFeatureNotSupportedException; 021import java.sql.Statement; 022import java.util.List; 023import java.util.Map; 024import java.util.Map.Entry; 025import java.util.Properties; 026import java.util.Set; 027import java.util.Timer; 028import java.util.TimerTask; 029import java.util.concurrent.atomic.AtomicBoolean; 030 031/** 032 * A robust DataSource implementation. 033 * <p> 034 * <ul> 035 * <li>Manages the number of connections closing connections that have been idle for some time.</li> 036 * <li>Notifies when the datasource goes down and comes back up.</li> 037 * <li>Provides PreparedStatement caching</li> 038 * <li>Knows the busy connections</li> 039 * <li>Traces connections that have been leaked</li> 040 * </ul> 041 * </p> 042 */ 043public class ConnectionPool implements DataSourcePool { 044 045 private static final Logger logger = LoggerFactory.getLogger(ConnectionPool.class); 046 047 /** 048 * The name given to this dataSource. 049 */ 050 private final String name; 051 052 private final DataSourceConfig config; 053 054 /** 055 * Used to notify of changes to the DataSource status. 056 */ 057 private final DataSourceAlert notify; 058 059 /** 060 * Optional listener that can be notified when connections are got from and 061 * put back into the pool. 062 */ 063 private final DataSourcePoolListener poolListener; 064 065 /** 066 * Properties used to create a Connection. 067 */ 068 private final Properties connectionProps; 069 070 /** 071 * Queries, that are run for each connection on first open. 072 */ 073 private final List<String> initSql; 074 075 /** 076 * The jdbc connection url. 077 */ 078 private final String databaseUrl; 079 080 /** 081 * The jdbc driver. 082 */ 083 private final String databaseDriver; 084 085 /** 086 * The sql used to test a connection. 087 */ 088 private final String heartbeatsql; 089 090 private final int heartbeatFreqSecs; 091 092 private final int heartbeatTimeoutSeconds; 093 094 095 private final long trimPoolFreqMillis; 096 097 /** 098 * The transaction isolation level as per java.sql.Connection. 099 */ 100 private final int transactionIsolation; 101 102 /** 103 * The default autoCommit setting for Connections in this pool. 104 */ 105 private final boolean autoCommit; 106 107 private final boolean readOnly; 108 109 private final boolean failOnStart; 110 111 /** 112 * Max idle time in millis. 113 */ 114 private final int maxInactiveMillis; 115 116 /** 117 * Max age a connection is allowed in millis. 118 * A value of 0 means no limit (no trimming based on max age). 119 */ 120 private final long maxAgeMillis; 121 122 /** 123 * Flag set to true to capture stackTraces (can be expensive). 124 */ 125 private boolean captureStackTrace; 126 127 /** 128 * The max size of the stack trace to report. 129 */ 130 private final int maxStackTraceSize; 131 132 /** 133 * flag to indicate we have sent an alert message. 134 */ 135 private boolean dataSourceDownAlertSent; 136 137 /** 138 * The time the pool was last trimmed. 139 */ 140 private long lastTrimTime; 141 142 /** 143 * Assume that the DataSource is up. heartBeat checking will discover when 144 * it goes down, and comes back up again. 145 */ 146 private boolean dataSourceUp = true; 147 148 /** 149 * Stores the dataSourceDown-reason (if there is any) 150 */ 151 private SQLException dataSourceDownReason; 152 153 /** 154 * The current alert. 155 */ 156 private AtomicBoolean inWarningMode = new AtomicBoolean(); 157 158 /** 159 * The minimum number of connections this pool will maintain. 160 */ 161 private int minConnections; 162 163 /** 164 * The maximum number of connections this pool will grow to. 165 */ 166 private int maxConnections; 167 168 /** 169 * The number of connections to exceed before a warning Alert is fired. 170 */ 171 private int warningSize; 172 173 /** 174 * The time a thread will wait for a connection to become available. 175 */ 176 private final int waitTimeoutMillis; 177 178 /** 179 * The size of the preparedStatement cache; 180 */ 181 private int pstmtCacheSize; 182 183 private final PooledConnectionQueue queue; 184 185 private final Timer heartBeatTimer; 186 187 /** 188 * Used to find and close() leaked connections. Leaked connections are 189 * thought to be busy but have not been used for some time. Each time a 190 * connection is used it sets it's lastUsedTime. 191 */ 192 private long leakTimeMinutes; 193 194 public ConnectionPool(String name, DataSourceConfig params) { 195 this.config = params; 196 this.name = name; 197 this.notify = params.getAlert(); 198 this.poolListener = params.getListener(); 199 200 this.autoCommit = params.isAutoCommit(); 201 this.readOnly = params.isReadOnly(); 202 this.failOnStart = params.isFailOnStart(); 203 this.initSql = params.getInitSql(); 204 this.transactionIsolation = params.getIsolationLevel(); 205 206 this.maxInactiveMillis = 1000 * params.getMaxInactiveTimeSecs(); 207 this.maxAgeMillis = 60000 * params.getMaxAgeMinutes(); 208 this.leakTimeMinutes = params.getLeakTimeMinutes(); 209 this.captureStackTrace = params.isCaptureStackTrace(); 210 this.maxStackTraceSize = params.getMaxStackTraceSize(); 211 this.databaseDriver = params.getDriver(); 212 this.databaseUrl = params.getUrl(); 213 this.pstmtCacheSize = params.getPstmtCacheSize(); 214 215 this.minConnections = params.getMinConnections(); 216 this.maxConnections = params.getMaxConnections(); 217 this.waitTimeoutMillis = params.getWaitTimeoutMillis(); 218 this.heartbeatsql = params.getHeartbeatSql(); 219 this.heartbeatFreqSecs = params.getHeartbeatFreqSecs(); 220 this.heartbeatTimeoutSeconds = params.getHeartbeatTimeoutSeconds(); 221 this.trimPoolFreqMillis = 1000 * params.getTrimPoolFreqSecs(); 222 223 queue = new PooledConnectionQueue(this); 224 225 String un = params.getUsername(); 226 String pw = params.getPassword(); 227 if (un == null) { 228 throw new DataSourceConfigurationException("DataSource user is null?"); 229 } 230 if (pw == null) { 231 throw new DataSourceConfigurationException("DataSource password is null?"); 232 } 233 this.connectionProps = new Properties(); 234 this.connectionProps.setProperty("user", un); 235 this.connectionProps.setProperty("password", pw); 236 237 Map<String, String> customProperties = params.getCustomProperties(); 238 if (customProperties != null) { 239 Set<Entry<String, String>> entrySet = customProperties.entrySet(); 240 for (Entry<String, String> entry : entrySet) { 241 this.connectionProps.setProperty(entry.getKey(), entry.getValue()); 242 } 243 } 244 245 try { 246 initialise(); 247 int freqMillis = heartbeatFreqSecs * 1000; 248 heartBeatTimer = new Timer(name + ".heartBeat", true); 249 if (freqMillis > 0) { 250 heartBeatTimer.scheduleAtFixedRate(new HeartBeatRunnable(), freqMillis, freqMillis); 251 } 252 } catch (SQLException ex) { 253 throw new DataSourceInitialiseException("Error initialising DataSource", ex); 254 } 255 } 256 257 class HeartBeatRunnable extends TimerTask { 258 @Override 259 public void run() { 260 checkDataSource(); 261 } 262 } 263 264 265 @Override 266 public java.util.logging.Logger getParentLogger() throws SQLFeatureNotSupportedException { 267 throw new SQLFeatureNotSupportedException("We do not support java.util.logging"); 268 } 269 270 private void initialise() throws SQLException { 271 272 // Ensure database driver is loaded 273 try { 274 ClassLoader contextLoader = Thread.currentThread().getContextClassLoader(); 275 if (contextLoader != null) { 276 Class.forName(databaseDriver, true, contextLoader); 277 } else { 278 Class.forName(databaseDriver, true, this.getClass().getClassLoader()); 279 } 280 } catch (Throwable e) { 281 throw new IllegalStateException("Problem loading Database Driver [" + this.databaseDriver + "]: " + e.getMessage(), e); 282 } 283 284 String transIsolation = TransactionIsolation.getDescription(transactionIsolation); 285 286 //noinspection StringBufferReplaceableByString 287 StringBuilder sb = new StringBuilder(70); 288 sb.append("DataSourcePool [").append(name); 289 sb.append("] autoCommit[").append(autoCommit); 290 sb.append("] transIsolation[").append(transIsolation); 291 sb.append("] min[").append(minConnections); 292 sb.append("] max[").append(maxConnections).append("]"); 293 294 logger.info(sb.toString()); 295 296 if (config.useInitDatabase()) { 297 initialiseDatabase(); 298 } 299 300 try { 301 queue.ensureMinimumConnections(); 302 } catch (SQLException e) { 303 if (failOnStart) { 304 throw e; 305 } 306 logger.error("Error trying to ensure minimum connections. Maybe db server is down.", e); 307 } 308 } 309 310 /** 311 * Initialise the database using the owner credentials if we can't connect using the normal credentials. 312 * <p> 313 * That is, if we think the username doesn't exist in the DB, initialise the DB using the owner credentials. 314 * </p> 315 */ 316 private void initialiseDatabase() throws SQLException { 317 try (Connection connection = createUnpooledConnection(connectionProps, false)) { 318 // successfully obtained a connection so skip initDatabase 319 connection.clearWarnings(); 320 } catch (SQLException e) { 321 logger.info("Obtaining connection using ownerUsername:{} to initialise database", config.getOwnerUsername()); 322 // expected when user does not exists, obtain a connection using owner credentials 323 try (Connection ownerConnection = createUnpooledConnection(config.getOwnerUsername(), config.getOwnerPassword())) { 324 // initialise the DB (typically create the user/role using the owner credentials etc) 325 InitDatabase initDatabase = config.getInitDatabase(); 326 initDatabase.run(ownerConnection, config); 327 ownerConnection.commit(); 328 } catch (SQLException e2) { 329 throw new SQLException("Failed to run InitDatabase with ownerUsername:" + config.getOwnerUsername(), e2); 330 } 331 } 332 } 333 334 /** 335 * Returns false. 336 */ 337 @Override 338 public boolean isWrapperFor(Class<?> arg0) throws SQLException { 339 return false; 340 } 341 342 /** 343 * Not Implemented. 344 */ 345 @Override 346 public <T> T unwrap(Class<T> arg0) throws SQLException { 347 throw new SQLException("Not Implemented"); 348 } 349 350 /** 351 * Return the dataSource name. 352 */ 353 @Override 354 public String getName() { 355 return name; 356 } 357 358 /** 359 * Return the max size of stack traces used when trying to find connection pool leaks. 360 * <p> 361 * This is only used when {@link #isCaptureStackTrace()} is true. 362 * </p> 363 */ 364 int getMaxStackTraceSize() { 365 return maxStackTraceSize; 366 } 367 368 /** 369 * Returns false when the dataSource is down. 370 */ 371 @Override 372 public boolean isDataSourceUp() { 373 return dataSourceUp; 374 } 375 376 @Override 377 public SQLException getDataSourceDownReason() { 378 return dataSourceDownReason; 379 } 380 381 /** 382 * Called when the pool hits the warning level. 383 */ 384 protected void notifyWarning(String msg) { 385 386 if (inWarningMode.compareAndSet(false, true)) { 387 // send an Error to the event log... 388 logger.warn(msg); 389 if (notify != null) { 390 notify.dataSourceWarning(this, msg); 391 } 392 } 393 } 394 395 private synchronized void notifyDataSourceIsDown(SQLException ex) { 396 397 if (dataSourceUp) { 398 reset(); 399 } 400 dataSourceUp = false; 401 if (ex != null) { 402 dataSourceDownReason = ex; 403 } 404 if (!dataSourceDownAlertSent) { 405 dataSourceDownAlertSent = true; 406 logger.error("FATAL: DataSourcePool [" + name + "] is down or has network error!!!", ex); 407 if (notify != null) { 408 notify.dataSourceDown(this, ex); 409 } 410 } 411 } 412 413 private synchronized void notifyDataSourceIsUp() { 414 if (dataSourceDownAlertSent) { 415 // set to false here, so that a getConnection() call in DataSourceAlert.dataSourceUp 416 // in same thread does not fire the event again (and end in recursion) 417 // all other threads will be blocked, becasue method is synchronized. 418 dataSourceDownAlertSent = false; 419 logger.error("RESOLVED FATAL: DataSourcePool [" + name + "] is back up!"); 420 if (notify != null) { 421 notify.dataSourceUp(this); 422 } 423 424 } else if (!dataSourceUp) { 425 logger.info("DataSourcePool [" + name + "] is back up!"); 426 } 427 428 if (!dataSourceUp) { 429 dataSourceUp = true; 430 dataSourceDownReason = null; 431 reset(); 432 } 433 } 434 435 /** 436 * Trim connections (in the free list) based on idle time and maximum age. 437 */ 438 private void trimIdleConnections() { 439 if (System.currentTimeMillis() > (lastTrimTime + trimPoolFreqMillis)) { 440 try { 441 queue.trim(maxInactiveMillis, maxAgeMillis); 442 lastTrimTime = System.currentTimeMillis(); 443 } catch (Exception e) { 444 logger.error("Error trying to trim idle connections", e); 445 } 446 } 447 } 448 449 /** 450 * Check the dataSource is up. Trim connections. 451 * <p> 452 * This is called by the HeartbeatRunnable which should be scheduled to 453 * run periodically (every heartbeatFreqSecs seconds actually). 454 * </p> 455 */ 456 private void checkDataSource() { 457 458 // first trim idle connections 459 trimIdleConnections(); 460 461 Connection conn = null; 462 try { 463 // Get a connection from the pool and test it 464 conn = getConnection(); 465 if (testConnection(conn)) { 466 notifyDataSourceIsUp(); 467 468 } else { 469 notifyDataSourceIsDown(null); 470 } 471 472 } catch (SQLException ex) { 473 notifyDataSourceIsDown(ex); 474 475 } finally { 476 try { 477 if (conn != null) { 478 conn.close(); 479 } 480 } catch (SQLException ex) { 481 logger.warn("Can't close connection in checkDataSource!"); 482 } 483 } 484 } 485 486 /** 487 * Initializes the connection we got from the driver. 488 */ 489 private void initConnection(Connection conn) throws SQLException { 490 conn.setAutoCommit(autoCommit); 491 // isolation level is set globally for all connections (at least for H2) 492 // and you will need admin rights - so we do not change it, if it already 493 // matches. 494 if (conn.getTransactionIsolation() != transactionIsolation) { 495 conn.setTransactionIsolation(transactionIsolation); 496 } 497 if (readOnly) { 498 conn.setReadOnly(readOnly); 499 } 500 if (initSql != null) { 501 for (String query : initSql) { 502 try (Statement stmt = conn.createStatement()) { 503 stmt.execute(query); 504 } 505 } 506 } 507 } 508 509 /** 510 * Create an un-pooled connection with the given username and password. 511 */ 512 public Connection createUnpooledConnection(String username, String password) throws SQLException { 513 514 Properties properties = new Properties(connectionProps); 515 properties.setProperty("user", username); 516 properties.setProperty("password", password); 517 return createUnpooledConnection(properties, true); 518 } 519 520 /** 521 * Create an un-pooled connection. 522 */ 523 public Connection createUnpooledConnection() throws SQLException { 524 return createUnpooledConnection(connectionProps, true); 525 } 526 527 private Connection createUnpooledConnection(Properties properties, boolean notifyIsDown) throws SQLException { 528 try { 529 Connection conn = DriverManager.getConnection(databaseUrl, properties); 530 initConnection(conn); 531 return conn; 532 533 } catch (SQLException ex) { 534 if (notifyIsDown) { 535 notifyDataSourceIsDown(null); 536 } 537 throw ex; 538 } 539 } 540 541 /** 542 * Set a new maximum size. The pool should respect this new maximum 543 * immediately and not require a restart. You may want to increase the 544 * maxConnections if the pool gets large and hits the warning level. 545 */ 546 @Override 547 public void setMaxSize(int max) { 548 queue.setMaxSize(max); 549 this.maxConnections = max; 550 } 551 552 /** 553 * Return the max size this pool can grow to. 554 */ 555 public int getMaxSize() { 556 return maxConnections; 557 } 558 559 /** 560 * Set the min size this pool should maintain. 561 */ 562 public void setMinSize(int min) { 563 queue.setMinSize(min); 564 this.minConnections = min; 565 } 566 567 /** 568 * Return the min size this pool should maintain. 569 */ 570 public int getMinSize() { 571 return minConnections; 572 } 573 574 /** 575 * Set a new maximum size. The pool should respect this new maximum 576 * immediately and not require a restart. You may want to increase the 577 * maxConnections if the pool gets large and hits the warning and or alert 578 * levels. 579 */ 580 @Override 581 public void setWarningSize(int warningSize) { 582 queue.setWarningSize(warningSize); 583 this.warningSize = warningSize; 584 } 585 586 /** 587 * Return the warning size. When the pool hits this size it can send a 588 * notify message to an administrator. 589 */ 590 @Override 591 public int getWarningSize() { 592 return warningSize; 593 } 594 595 /** 596 * Return the time in millis that threads will wait when the pool has hit 597 * the max size. These threads wait for connections to be returned by the 598 * busy connections. 599 */ 600 public int getWaitTimeoutMillis() { 601 return waitTimeoutMillis; 602 } 603 604 /** 605 * Return the time after which inactive connections are trimmed. 606 */ 607 public int getMaxInactiveMillis() { 608 return maxInactiveMillis; 609 } 610 611 /** 612 * Return the maximum age a connection is allowed to be before it is trimmed 613 * out of the pool. This value can be 0 which means there is no maximum age. 614 */ 615 public long getMaxAgeMillis() { 616 return maxAgeMillis; 617 } 618 619 private boolean testConnection(Connection conn) throws SQLException { 620 621 if (heartbeatsql == null) { 622 return conn.isValid(heartbeatTimeoutSeconds); 623 } 624 Statement stmt = null; 625 ResultSet rset = null; 626 try { 627 // It should only error IF the DataSource is down or a network issue 628 stmt = conn.createStatement(); 629 if (heartbeatTimeoutSeconds > 0) { 630 stmt.setQueryTimeout(heartbeatTimeoutSeconds); 631 } 632 rset = stmt.executeQuery(heartbeatsql); 633 conn.commit(); 634 635 return true; 636 637 } finally { 638 try { 639 if (rset != null) { 640 rset.close(); 641 } 642 } catch (SQLException e) { 643 logger.error(null, e); 644 } 645 try { 646 if (stmt != null) { 647 stmt.close(); 648 } 649 } catch (SQLException e) { 650 logger.error(null, e); 651 } 652 } 653 } 654 655 /** 656 * Make sure the connection is still ok to use. If not then remove it from 657 * the pool. 658 */ 659 boolean validateConnection(PooledConnection conn) { 660 try { 661 return testConnection(conn); 662 663 } catch (Exception e) { 664 logger.warn("heartbeatsql test failed on connection[" + conn.getName() + "]"); 665 return false; 666 } 667 } 668 669 /** 670 * Called by the PooledConnection themselves, returning themselves to the 671 * pool when they have been finished with. 672 * <p> 673 * Note that connections may not be added back to the pool if returnToPool 674 * is false or if they where created before the recycleTime. In both of 675 * these cases the connection is fully closed and not pooled. 676 * </p> 677 * 678 * @param pooledConnection the returning connection 679 */ 680 void returnConnection(PooledConnection pooledConnection) { 681 682 // return a normal 'good' connection 683 returnTheConnection(pooledConnection, false); 684 } 685 686 /** 687 * This is a bad connection and must be removed from the pool's busy list and fully closed. 688 */ 689 void returnConnectionForceClose(PooledConnection pooledConnection) { 690 691 returnTheConnection(pooledConnection, true); 692 } 693 694 /** 695 * Return connection. If forceClose is true then this is a bad connection that 696 * must be removed and closed fully. 697 */ 698 private void returnTheConnection(PooledConnection pooledConnection, boolean forceClose) { 699 700 if (poolListener != null && !forceClose) { 701 poolListener.onBeforeReturnConnection(pooledConnection); 702 } 703 queue.returnPooledConnection(pooledConnection, forceClose); 704 705 if (forceClose) { 706 // Got a bad connection so check the pool 707 checkDataSource(); 708 } 709 } 710 711 /** 712 * Collect statistics of a connection that is fully closing 713 */ 714 void reportClosingConnection(PooledConnection pooledConnection) { 715 716 queue.reportClosingConnection(pooledConnection); 717 } 718 719 /** 720 * Returns information describing connections that are currently being used. 721 */ 722 public String getBusyConnectionInformation() { 723 724 return queue.getBusyConnectionInformation(); 725 } 726 727 /** 728 * Dumps the busy connection information to the logs. 729 * <p> 730 * This includes the stackTrace elements if they are being captured. This is 731 * useful when needing to look a potential connection pool leaks. 732 * </p> 733 */ 734 public void dumpBusyConnectionInformation() { 735 736 queue.dumpBusyConnectionInformation(); 737 } 738 739 /** 740 * Close any busy connections that have not been used for some time. 741 * <p> 742 * These connections are considered to have leaked from the connection pool. 743 * </p> 744 * <p> 745 * Connection leaks occur when code doesn't ensure that connections are 746 * closed() after they have been finished with. There should be an 747 * appropriate try catch finally block to ensure connections are always 748 * closed and put back into the pool. 749 * </p> 750 */ 751 public void closeBusyConnections(long leakTimeMinutes) { 752 753 queue.closeBusyConnections(leakTimeMinutes); 754 } 755 756 /** 757 * Grow the pool by creating a new connection. The connection can either be 758 * added to the available list, or returned. 759 * <p> 760 * This method is protected by synchronization in calling methods. 761 * </p> 762 */ 763 PooledConnection createConnectionForQueue(int connId) throws SQLException { 764 765 try { 766 Connection c = createUnpooledConnection(); 767 768 PooledConnection pc = new PooledConnection(this, connId, c); 769 pc.resetForUse(); 770 771 if (!dataSourceUp) { 772 notifyDataSourceIsUp(); 773 } 774 return pc; 775 776 } catch (SQLException ex) { 777 notifyDataSourceIsDown(ex); 778 throw ex; 779 } 780 } 781 782 /** 783 * Close all the connections in the pool. 784 * <p> 785 * <ul> 786 * <li>Checks that the database is up. 787 * <li>Resets the Alert level. 788 * <li>Closes busy connections that have not been used for some time (aka 789 * leaks). 790 * <li>This closes all the currently available connections. 791 * <li>Busy connections are closed when they are returned to the pool. 792 * </ul> 793 * </p> 794 */ 795 public void reset() { 796 queue.reset(leakTimeMinutes); 797 inWarningMode.set(false); 798 } 799 800 /** 801 * Return a pooled connection. 802 */ 803 @Override 804 public Connection getConnection() throws SQLException { 805 return getPooledConnection(); 806 } 807 808 /** 809 * Get a connection from the pool. 810 * <p> 811 * This will grow the pool if all the current connections are busy. This 812 * will go into a wait if the pool has hit its maximum size. 813 * </p> 814 */ 815 private PooledConnection getPooledConnection() throws SQLException { 816 817 PooledConnection c = queue.getPooledConnection(); 818 819 if (captureStackTrace) { 820 c.setStackTrace(Thread.currentThread().getStackTrace()); 821 } 822 823 if (poolListener != null) { 824 poolListener.onAfterBorrowConnection(c); 825 } 826 return c; 827 } 828 829 /** 830 * Send a message to the DataSourceAlertListener to test it. This is so that 831 * you can make sure the alerter is configured correctly etc. 832 */ 833 public void testAlert() { 834 835 String msg = "Just testing if alert message is sent successfully."; 836 837 if (notify != null) { 838 notify.dataSourceWarning(this, msg); 839 } 840 } 841 842 /** 843 * This will close all the free connections, and then go into a wait loop, 844 * waiting for the busy connections to be freed. 845 * <p> 846 * <p> 847 * The DataSources's should be shutdown AFTER thread pools. Leaked 848 * Connections are not waited on, as that would hang the server. 849 * </p> 850 */ 851 @Override 852 public void shutdown(boolean deregisterDriver) { 853 heartBeatTimer.cancel(); 854 queue.shutdown(); 855 if (deregisterDriver) { 856 deregisterDriver(); 857 } 858 } 859 860 /** 861 * Return the default autoCommit setting Connections in this pool will use. 862 * 863 * @return true if the pool defaults autoCommit to true 864 */ 865 @Override 866 public boolean isAutoCommit() { 867 return autoCommit; 868 } 869 870 /** 871 * Return the default transaction isolation level connections in this pool 872 * should have. 873 * 874 * @return the default transaction isolation level 875 */ 876 int getTransactionIsolation() { 877 return transactionIsolation; 878 } 879 880 /** 881 * Return true if the connection pool is currently capturing the StackTrace 882 * when connections are 'got' from the pool. 883 * <p> 884 * This is set to true to help diagnose connection pool leaks. 885 * </p> 886 */ 887 public boolean isCaptureStackTrace() { 888 return captureStackTrace; 889 } 890 891 /** 892 * Set this to true means that the StackElements are captured every time a 893 * connection is retrieved from the pool. This can be used to identify 894 * connection pool leaks. 895 */ 896 public void setCaptureStackTrace(boolean captureStackTrace) { 897 this.captureStackTrace = captureStackTrace; 898 } 899 900 /** 901 * Create an un-pooled connection with the given username and password. 902 * <p> 903 * This uses the default isolation level and autocommit mode. 904 */ 905 @Override 906 public Connection getConnection(String username, String password) throws SQLException { 907 908 Properties props = new Properties(); 909 props.putAll(connectionProps); 910 props.setProperty("user", username); 911 props.setProperty("password", password); 912 Connection conn = DriverManager.getConnection(databaseUrl, props); 913 initConnection(conn); 914 return conn; 915 } 916 917 /** 918 * Not implemented and shouldn't be used. 919 */ 920 @Override 921 public int getLoginTimeout() throws SQLException { 922 throw new SQLException("Method not supported"); 923 } 924 925 /** 926 * Not implemented and shouldn't be used. 927 */ 928 @Override 929 public void setLoginTimeout(int seconds) throws SQLException { 930 throw new SQLException("Method not supported"); 931 } 932 933 /** 934 * Returns null. 935 */ 936 @Override 937 public PrintWriter getLogWriter() { 938 return null; 939 } 940 941 /** 942 * Not implemented. 943 */ 944 @Override 945 public void setLogWriter(PrintWriter writer) throws SQLException { 946 throw new SQLException("Method not supported"); 947 } 948 949 /** 950 * For detecting and closing leaked connections. Connections that have been 951 * busy for more than leakTimeMinutes are considered leaks and will be 952 * closed on a reset(). 953 * <p> 954 * If you want to use a connection for that longer then you should consider 955 * creating an unpooled connection or setting longRunning to true on that 956 * connection. 957 * </p> 958 */ 959 public void setLeakTimeMinutes(long leakTimeMinutes) { 960 this.leakTimeMinutes = leakTimeMinutes; 961 } 962 963 /** 964 * Return the number of minutes after which a busy connection could be 965 * considered leaked from the connection pool. 966 */ 967 public long getLeakTimeMinutes() { 968 return leakTimeMinutes; 969 } 970 971 /** 972 * Return the preparedStatement cache size. 973 */ 974 public int getPstmtCacheSize() { 975 return pstmtCacheSize; 976 } 977 978 /** 979 * Set the preparedStatement cache size. 980 */ 981 public void setPstmtCacheSize(int pstmtCacheSize) { 982 this.pstmtCacheSize = pstmtCacheSize; 983 } 984 985 /** 986 * Return the current status of the connection pool. 987 * <p> 988 * If you pass reset = true then the counters such as 989 * hitCount, waitCount and highWaterMark are reset. 990 * </p> 991 */ 992 @Override 993 public PoolStatus getStatus(boolean reset) { 994 return queue.getStatus(reset); 995 } 996 997 /** 998 * Return the aggregated load statistics collected on all the connections in the pool. 999 */ 1000 @Override 1001 public PoolStatistics getStatistics(boolean reset) { 1002 return queue.getStatistics(reset); 1003 } 1004 1005 /** 1006 * Deregister the JDBC driver. 1007 */ 1008 private void deregisterDriver() { 1009 try { 1010 logger.debug("Deregister the JDBC driver " + this.databaseDriver); 1011 DriverManager.deregisterDriver(DriverManager.getDriver(this.databaseUrl)); 1012 } catch (SQLException e) { 1013 logger.warn("Error trying to deregister the JDBC driver " + this.databaseDriver, e); 1014 } 1015 } 1016 1017 public static class Status implements PoolStatus { 1018 1019 private final int minSize; 1020 private final int maxSize; 1021 private final int free; 1022 private final int busy; 1023 private final int waiting; 1024 private final int highWaterMark; 1025 private final int waitCount; 1026 private final int hitCount; 1027 1028 protected Status(int minSize, int maxSize, int free, int busy, int waiting, int highWaterMark, int waitCount, int hitCount) { 1029 this.minSize = minSize; 1030 this.maxSize = maxSize; 1031 this.free = free; 1032 this.busy = busy; 1033 this.waiting = waiting; 1034 this.highWaterMark = highWaterMark; 1035 this.waitCount = waitCount; 1036 this.hitCount = hitCount; 1037 } 1038 1039 public String toString() { 1040 return "min[" + minSize + "] max[" + maxSize + "] free[" + free + "] busy[" + busy + "] waiting[" + waiting 1041 + "] highWaterMark[" + highWaterMark + "] waitCount[" + waitCount + "] hitCount[" + hitCount + "]"; 1042 } 1043 1044 /** 1045 * Return the min pool size. 1046 */ 1047 @Override 1048 public int getMinSize() { 1049 return minSize; 1050 } 1051 1052 /** 1053 * Return the max pool size. 1054 */ 1055 @Override 1056 public int getMaxSize() { 1057 return maxSize; 1058 } 1059 1060 /** 1061 * Return the current number of free connections in the pool. 1062 */ 1063 @Override 1064 public int getFree() { 1065 return free; 1066 } 1067 1068 /** 1069 * Return the current number of busy connections in the pool. 1070 */ 1071 @Override 1072 public int getBusy() { 1073 return busy; 1074 } 1075 1076 /** 1077 * Return the current number of threads waiting for a connection. 1078 */ 1079 @Override 1080 public int getWaiting() { 1081 return waiting; 1082 } 1083 1084 /** 1085 * Return the high water mark of busy connections. 1086 */ 1087 @Override 1088 public int getHighWaterMark() { 1089 return highWaterMark; 1090 } 1091 1092 /** 1093 * Return the total number of times a thread had to wait. 1094 */ 1095 @Override 1096 public int getWaitCount() { 1097 return waitCount; 1098 } 1099 1100 /** 1101 * Return the total number of times there was an attempt to get a 1102 * connection. 1103 * <p> 1104 * If the attempt to get a connection failed with a timeout or other 1105 * exception those attempts are still included in this hit count. 1106 * </p> 1107 */ 1108 @Override 1109 public int getHitCount() { 1110 return hitCount; 1111 } 1112 1113 } 1114 1115}