001package org.avaje.datasource.pool; 002 003import org.avaje.datasource.delegate.ConnectionDelegator; 004import org.slf4j.Logger; 005import org.slf4j.LoggerFactory; 006 007import java.sql.CallableStatement; 008import java.sql.Connection; 009import java.sql.DatabaseMetaData; 010import java.sql.PreparedStatement; 011import java.sql.SQLException; 012import java.sql.SQLWarning; 013import java.sql.Savepoint; 014import java.sql.Statement; 015import java.util.ArrayList; 016import java.util.Arrays; 017import java.util.Map; 018 019/** 020 * Is a connection that belongs to a DataSourcePool. 021 * <p/> 022 * <p> 023 * It is designed to be part of DataSourcePool. Closing the connection puts it 024 * back into the pool. 025 * </p> 026 * <p/> 027 * <p> 028 * It defaults autoCommit and Transaction Isolation to the defaults of the 029 * DataSourcePool. 030 * </p> 031 * <p/> 032 * <p> 033 * It has caching of Statements and PreparedStatements. Remembers the last 034 * statement that was executed. Keeps statistics on how long it is in use. 035 * </p> 036 */ 037public class PooledConnection extends ConnectionDelegator { 038 039 private static final Logger logger = LoggerFactory.getLogger(PooledConnection.class); 040 041 private static final String IDLE_CONNECTION_ACCESSED_ERROR = "Pooled Connection has been accessed whilst idle in the pool, via method: "; 042 043 /** 044 * Marker for when connection is closed due to exceeding the max allowed age. 045 */ 046 private static final String REASON_MAXAGE = "maxAge"; 047 048 /** 049 * Marker for when connection is closed due to exceeding the max inactive time. 050 */ 051 private static final String REASON_IDLE = "idleTime"; 052 053 /** 054 * Marker for when the connection is closed due to a reset. 055 */ 056 private static final String REASON_RESET = "reset"; 057 058 /** 059 * Set when connection is idle in the pool. In general when in the pool the 060 * connection should not be modified. 061 */ 062 private static final int STATUS_IDLE = 88; 063 064 /** 065 * Set when connection given to client. 066 */ 067 private static final int STATUS_ACTIVE = 89; 068 069 /** 070 * Set when commit() or rollback() called. 071 */ 072 private static final int STATUS_ENDED = 87; 073 074 /** 075 * Name used to identify the PooledConnection for logging. 076 */ 077 private final String name; 078 079 /** 080 * The pool this connection belongs to. 081 */ 082 private final ConnectionPool pool; 083 084 /** 085 * The underlying connection. 086 */ 087 private final Connection connection; 088 089 /** 090 * The time this connection was created. 091 */ 092 private final long creationTime; 093 094 /** 095 * Cache of the PreparedStatements 096 */ 097 private final PstmtCache pstmtCache; 098 099 private final Object pstmtMonitor = new Object(); 100 101 /** 102 * Helper for statistics collection. 103 */ 104 private final PooledConnectionStatistics stats = new PooledConnectionStatistics(); 105 106 /** 107 * The status of the connection. IDLE, ACTIVE or ENDED. 108 */ 109 private int status = STATUS_IDLE; 110 111 /** 112 * The reason for a connection closing. 113 */ 114 private String closeReason; 115 116 /** 117 * Set this to true if the connection will be busy for a long time. 118 * <p> 119 * This means it should skip the suspected connection pool leak checking. 120 * </p> 121 */ 122 private boolean longRunning; 123 124 /** 125 * Flag to indicate that this connection had errors and should be checked to 126 * make sure it is okay. 127 */ 128 private boolean hadErrors; 129 130 /** 131 * The last start time. When the connection was given to a thread. 132 */ 133 private long startUseTime; 134 135 /** 136 * The last end time of this connection. This is to calculate the usage 137 * time. 138 */ 139 private long lastUseTime; 140 141 private long exeStartNanos; 142 143 /** 144 * The last statement executed by this connection. 145 */ 146 private String lastStatement; 147 148 /** 149 * The non avaje method that created the connection. 150 */ 151 private String createdByMethod; 152 153 /** 154 * Used to find connection pool leaks. 155 */ 156 private StackTraceElement[] stackTrace; 157 158 private final int maxStackTrace; 159 160 /** 161 * Slot position in the BusyConnectionBuffer. 162 */ 163 private int slotId; 164 165 private boolean resetIsolationReadOnlyRequired; 166 167 168 /** 169 * Construct the connection that can refer back to the pool it belongs to. 170 * <p> 171 * close() will return the connection back to the pool , while 172 * closeDestroy() will close() the underlining connection properly. 173 * </p> 174 */ 175 public PooledConnection(ConnectionPool pool, int uniqueId, Connection connection) { 176 super(connection); 177 178 this.pool = pool; 179 this.connection = connection; 180 this.name = pool.getName() + "" + uniqueId; 181 this.pstmtCache = new PstmtCache(pool.getPstmtCacheSize()); 182 this.maxStackTrace = pool.getMaxStackTraceSize(); 183 this.creationTime = System.currentTimeMillis(); 184 this.lastUseTime = creationTime; 185 } 186 187 /** 188 * For testing the pool without real connections. 189 */ 190 protected PooledConnection(String name) { 191 super(null); 192 this.name = name; 193 this.pool = null; 194 this.connection = null; 195 this.pstmtCache = null; 196 this.maxStackTrace = 0; 197 this.creationTime = System.currentTimeMillis(); 198 this.lastUseTime = creationTime; 199 } 200 201 /** 202 * Return the slot position in the busy buffer. 203 */ 204 int getSlotId() { 205 return slotId; 206 } 207 208 /** 209 * Set the slot position in the busy buffer. 210 */ 211 void setSlotId(int slotId) { 212 this.slotId = slotId; 213 } 214 215 /** 216 * Return a string to identify the connection. 217 */ 218 String getName() { 219 return name; 220 } 221 222 private String getNameSlot() { 223 return name + ":" + slotId; 224 } 225 226 public String toString() { 227 return getDescription(); 228 } 229 230 private long getBusySeconds() { 231 return (System.currentTimeMillis() - startUseTime) / 1000; 232 } 233 234 String getDescription() { 235 return "name[" + name + "] slot[" + slotId + "] startTime[" + getStartUseTime() + "] busySeconds[" + getBusySeconds() + "] createdBy[" + getCreatedByMethod() + "] stmt[" + getLastStatement() + "]"; 236 } 237 238 String getFullDescription() { 239 return "name[" + name + "] slot[" + slotId + "] startTime[" + getStartUseTime() + "] busySeconds[" + getBusySeconds() + "] stackTrace[" + getStackTraceAsString() + "] stmt[" + getLastStatement() + "]"; 240 } 241 242 PooledConnectionStatistics getStatistics() { 243 return stats; 244 } 245 246 /** 247 * Return true if the connection should be treated as long running (skip connection pool leak check). 248 */ 249 boolean isLongRunning() { 250 return longRunning; 251 } 252 253 /** 254 * Set this to true if the connection is a long running connection and should skip the 255 * 'suspected connection pool leak' checking. 256 */ 257 public void setLongRunning(boolean longRunning) { 258 this.longRunning = longRunning; 259 } 260 261 /** 262 * Close the connection fully NOT putting in back into the pool. 263 * <p> 264 * The logErrors parameter exists so that expected errors are not logged 265 * such as when the database is known to be down. 266 * </p> 267 * 268 * @param logErrors if false then don't log errors when closing 269 */ 270 void closeConnectionFully(boolean logErrors) { 271 272 if (pool != null) { 273 // allow collection of load statistics 274 pool.reportClosingConnection(this); 275 } 276 277 if (logger.isDebugEnabled()) { 278 logger.debug("Closing Connection[{}] slot[{}] reason[{}] stats: {} , pstmtStats: {} ", name, slotId, closeReason, stats.getValues(false), pstmtCache.getDescription()); 279 } 280 281 try { 282 if (connection.isClosed()) { 283 // Typically the JDBC Driver has its own JVM shutdown hook and already 284 // closed the connections in our DataSource pool so making this DEBUG level 285 logger.debug("Closing Connection[{}] that is already closed?", name); 286 return; 287 } 288 } catch (SQLException ex) { 289 if (logErrors) { 290 logger.error("Error checking if connection [" + getNameSlot() + "] is closed", ex); 291 } 292 } 293 294 try { 295 for (ExtendedPreparedStatement ps : pstmtCache.values()) { 296 ps.closeDestroy(); 297 } 298 299 } catch (SQLException ex) { 300 if (logErrors) { 301 logger.warn("Error when closing connection Statements", ex); 302 } 303 } 304 305 try { 306 connection.close(); 307 } catch (SQLException ex) { 308 if (logErrors || logger.isDebugEnabled()) { 309 logger.error("Error when fully closing connection [" + getFullDescription() + "]", ex); 310 } 311 } 312 } 313 314 /** 315 * Creates a wrapper ExtendedStatement so that I can get the executed sql. I 316 * want to do this so that I can get the slowest query statments etc, and 317 * log that information. 318 */ 319 public Statement createStatement() throws SQLException { 320 if (status == STATUS_IDLE) { 321 throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "createStatement()"); 322 } 323 try { 324 return connection.createStatement(); 325 } catch (SQLException ex) { 326 markWithError(); 327 throw ex; 328 } 329 } 330 331 public Statement createStatement(int resultSetType, int resultSetConcurreny) throws SQLException { 332 if (status == STATUS_IDLE) { 333 throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "createStatement()"); 334 } 335 try { 336 return connection.createStatement(resultSetType, resultSetConcurreny); 337 338 } catch (SQLException ex) { 339 markWithError(); 340 throw ex; 341 } 342 } 343 344 /** 345 * Return a PreparedStatement back into the cache. 346 */ 347 void returnPreparedStatement(ExtendedPreparedStatement pstmt) { 348 349 synchronized (pstmtMonitor) { 350 if (!pstmtCache.returnStatement(pstmt)) { 351 try { 352 // Already an entry in the cache with the exact same SQL... 353 pstmt.closeDestroy(); 354 355 } catch (SQLException e) { 356 logger.error("Error closing Pstmt", e); 357 } 358 } 359 } 360 } 361 362 /** 363 * This will try to use a cache of PreparedStatements. 364 */ 365 public PreparedStatement prepareStatement(String sql, int returnKeysFlag) throws SQLException { 366 String cacheKey = sql + returnKeysFlag; 367 return prepareStatement(sql, true, returnKeysFlag, cacheKey); 368 } 369 370 /** 371 * This will try to use a cache of PreparedStatements. 372 */ 373 public PreparedStatement prepareStatement(String sql) throws SQLException { 374 return prepareStatement(sql, false, 0, sql); 375 } 376 377 /** 378 * This will try to use a cache of PreparedStatements. 379 */ 380 private PreparedStatement prepareStatement(String sql, boolean useFlag, int flag, String cacheKey) throws SQLException { 381 382 if (status == STATUS_IDLE) { 383 throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "prepareStatement()"); 384 } 385 try { 386 synchronized (pstmtMonitor) { 387 lastStatement = sql; 388 389 // try to get a matching cached PStmt from the cache. 390 ExtendedPreparedStatement pstmt = pstmtCache.remove(cacheKey); 391 392 if (pstmt != null) { 393 return pstmt; 394 } 395 396 // create a new PreparedStatement 397 PreparedStatement actualPstmt; 398 if (useFlag) { 399 actualPstmt = connection.prepareStatement(sql, flag); 400 } else { 401 actualPstmt = connection.prepareStatement(sql); 402 } 403 return new ExtendedPreparedStatement(this, actualPstmt, sql, cacheKey); 404 } 405 406 } catch (SQLException ex) { 407 markWithError(); 408 throw ex; 409 } 410 } 411 412 public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurreny) throws SQLException { 413 414 if (status == STATUS_IDLE) { 415 throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "prepareStatement()"); 416 } 417 try { 418 // no caching when creating PreparedStatements this way 419 lastStatement = sql; 420 return connection.prepareStatement(sql, resultSetType, resultSetConcurreny); 421 } catch (SQLException ex) { 422 markWithError(); 423 throw ex; 424 } 425 } 426 427 /** 428 * Reset the connection for returning to the client. Resets the status, 429 * startUseTime and hadErrors. 430 */ 431 void resetForUse() { 432 this.status = STATUS_ACTIVE; 433 this.startUseTime = System.currentTimeMillis(); 434 this.exeStartNanos = System.nanoTime(); 435 this.createdByMethod = null; 436 this.lastStatement = null; 437 this.hadErrors = false; 438 this.longRunning = false; 439 } 440 441 /** 442 * When an error occurs during use add it the connection. 443 * <p> 444 * Any PooledConnection that has an error is checked to make sure it works 445 * before it is placed back into the connection pool. 446 * </p> 447 */ 448 void markWithError() { 449 hadErrors = true; 450 } 451 452 /** 453 * close the connection putting it back into the connection pool. 454 * <p> 455 * Note that to ensure that the next transaction starts at the correct time 456 * a commit() or rollback() should be called. If neither has occured at this 457 * time then a rollback() is used (to end the transaction). 458 * </p> 459 * <p> 460 * To close the connection fully use closeConnectionFully(). 461 * </p> 462 */ 463 public void close() throws SQLException { 464 if (status == STATUS_IDLE) { 465 throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "close()"); 466 } 467 468 long durationNanos = System.nanoTime() - exeStartNanos; 469 stats.add(durationNanos, hadErrors); 470 471 if (hadErrors) { 472 if (!pool.validateConnection(this)) { 473 // the connection is BAD, remove it, close it and test the pool 474 pool.returnConnectionForceClose(this); 475 return; 476 } 477 } 478 479 try { 480 // reset the autoCommit back if client code changed it 481 if (connection.getAutoCommit() != pool.isAutoCommit()) { 482 connection.setAutoCommit(pool.isAutoCommit()); 483 } 484 // Generally resetting Isolation level seems expensive. 485 // Hence using resetIsolationReadOnlyRequired flag 486 // performance reasons. 487 if (resetIsolationReadOnlyRequired) { 488 resetIsolationReadOnly(); 489 resetIsolationReadOnlyRequired = false; 490 } 491 492 // the connection is assumed GOOD so put it back in the pool 493 lastUseTime = System.currentTimeMillis(); 494 // connection.clearWarnings(); 495 status = STATUS_IDLE; 496 pool.returnConnection(this); 497 498 } catch (Exception ex) { 499 // the connection is BAD, remove it, close it and test the pool 500 logger.warn("Error when trying to return connection to pool, closing fully.", ex); 501 pool.returnConnectionForceClose(this); 502 } 503 } 504 505 private void resetIsolationReadOnly() throws SQLException { 506 // reset the transaction isolation if the client code changed it 507 //noinspection MagicConstant 508 if (connection.getTransactionIsolation() != pool.getTransactionIsolation()) { 509 //noinspection MagicConstant 510 connection.setTransactionIsolation(pool.getTransactionIsolation()); 511 } 512 // reset readonly to false 513 if (connection.isReadOnly()) { 514 connection.setReadOnly(false); 515 } 516 } 517 518 protected void finalize() throws Throwable { 519 try { 520 if (connection != null && !connection.isClosed()) { 521 // connect leak? 522 logger.warn("Closing Connection on finalize() - {}", getFullDescription()); 523 closeConnectionFully(false); 524 } 525 } catch (Exception e) { 526 logger.error("Error when finalize is closing a connection? (unexpected)", e); 527 } 528 super.finalize(); 529 } 530 531 /** 532 * Return true if the connection is too old. 533 */ 534 private boolean exceedsMaxAge(long maxAgeMillis) { 535 if (maxAgeMillis > 0 && (creationTime < (System.currentTimeMillis() - maxAgeMillis))) { 536 this.closeReason = REASON_MAXAGE; 537 return true; 538 } 539 return false; 540 } 541 542 boolean shouldTrimOnReturn(long lastResetTime, long maxAgeMillis) { 543 if (creationTime <= lastResetTime) { 544 this.closeReason = REASON_RESET; 545 return true; 546 } 547 return exceedsMaxAge(maxAgeMillis); 548 } 549 550 /** 551 * Return true if the connection has been idle for too long or is too old. 552 */ 553 boolean shouldTrim(long usedSince, long createdSince) { 554 if (lastUseTime < usedSince) { 555 // been idle for too long so trim it 556 this.closeReason = REASON_IDLE; 557 return true; 558 } 559 if (createdSince > 0 && createdSince > creationTime) { 560 // exceeds max age so trim it 561 this.closeReason = REASON_MAXAGE; 562 return true; 563 } 564 return false; 565 } 566 567 /** 568 * Return the time the connection was passed to the client code. 569 * <p> 570 * Used to detect busy connections that could be leaks. 571 * </p> 572 */ 573 private long getStartUseTime() { 574 return startUseTime; 575 } 576 577 /** 578 * Returns the time the connection was last used. 579 * <p> 580 * Used to close connections that have been idle for some time. Typically 5 581 * minutes. 582 * </p> 583 */ 584 long getLastUsedTime() { 585 return lastUseTime; 586 } 587 588 /** 589 * Returns the last sql statement executed. 590 */ 591 private String getLastStatement() { 592 return lastStatement; 593 } 594 595 /** 596 * Called by ExtendedStatement to trace the sql being executed. 597 * <p> 598 * Note with addBatch() this will not really work. 599 * </p> 600 */ 601 void setLastStatement(String lastStatement) { 602 this.lastStatement = lastStatement; 603 if (logger.isTraceEnabled()) { 604 logger.trace(".setLastStatement[" + lastStatement + "]"); 605 } 606 } 607 608 609 /** 610 * Also note the read only status needs to be reset when put back into the 611 * pool. 612 */ 613 public void setReadOnly(boolean readOnly) throws SQLException { 614 // A bit loose not checking for STATUS_IDLE 615 // if (status == STATUS_IDLE) { 616 // throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + 617 // "setReadOnly()"); 618 // } 619 resetIsolationReadOnlyRequired = true; 620 connection.setReadOnly(readOnly); 621 } 622 623 /** 624 * Also note the Isolation level needs to be reset when put back into the 625 * pool. 626 */ 627 public void setTransactionIsolation(int level) throws SQLException { 628 if (status == STATUS_IDLE) { 629 throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "setTransactionIsolation()"); 630 } 631 try { 632 resetIsolationReadOnlyRequired = true; 633 connection.setTransactionIsolation(level); 634 } catch (SQLException ex) { 635 markWithError(); 636 throw ex; 637 } 638 } 639 640 // 641 // 642 // Simple wrapper methods which pass a method call onto the acutal 643 // connection object. These methods are safe-guarded to prevent use of 644 // the methods whilst the connection is in the connection pool. 645 // 646 // 647 public void clearWarnings() throws SQLException { 648 if (status == STATUS_IDLE) { 649 throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "clearWarnings()"); 650 } 651 connection.clearWarnings(); 652 } 653 654 public void commit() throws SQLException { 655 if (status == STATUS_IDLE) { 656 throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "commit()"); 657 } 658 try { 659 status = STATUS_ENDED; 660 connection.commit(); 661 } catch (SQLException ex) { 662 markWithError(); 663 throw ex; 664 } 665 } 666 667 public boolean getAutoCommit() throws SQLException { 668 if (status == STATUS_IDLE) { 669 throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "getAutoCommit()"); 670 } 671 return connection.getAutoCommit(); 672 } 673 674 public String getCatalog() throws SQLException { 675 if (status == STATUS_IDLE) { 676 throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "getCatalog()"); 677 } 678 return connection.getCatalog(); 679 } 680 681 public DatabaseMetaData getMetaData() throws SQLException { 682 if (status == STATUS_IDLE) { 683 throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "getMetaData()"); 684 } 685 return connection.getMetaData(); 686 } 687 688 public int getTransactionIsolation() throws SQLException { 689 if (status == STATUS_IDLE) { 690 throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "getTransactionIsolation()"); 691 } 692 return connection.getTransactionIsolation(); 693 } 694 695 public Map<String, Class<?>> getTypeMap() throws SQLException { 696 if (status == STATUS_IDLE) { 697 throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "getTypeMap()"); 698 } 699 return connection.getTypeMap(); 700 } 701 702 public SQLWarning getWarnings() throws SQLException { 703 if (status == STATUS_IDLE) { 704 throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "getWarnings()"); 705 } 706 return connection.getWarnings(); 707 } 708 709 public boolean isClosed() throws SQLException { 710 if (status == STATUS_IDLE) { 711 throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "isClosed()"); 712 } 713 return connection.isClosed(); 714 } 715 716 public boolean isReadOnly() throws SQLException { 717 if (status == STATUS_IDLE) { 718 throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "isReadOnly()"); 719 } 720 return connection.isReadOnly(); 721 } 722 723 public String nativeSQL(String sql) throws SQLException { 724 if (status == STATUS_IDLE) { 725 throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "nativeSQL()"); 726 } 727 lastStatement = sql; 728 return connection.nativeSQL(sql); 729 } 730 731 public CallableStatement prepareCall(String sql) throws SQLException { 732 if (status == STATUS_IDLE) { 733 throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "prepareCall()"); 734 } 735 lastStatement = sql; 736 return connection.prepareCall(sql); 737 } 738 739 public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurreny) throws SQLException { 740 if (status == STATUS_IDLE) { 741 throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "prepareCall()"); 742 } 743 lastStatement = sql; 744 return connection.prepareCall(sql, resultSetType, resultSetConcurreny); 745 } 746 747 public void rollback() throws SQLException { 748 if (status == STATUS_IDLE) { 749 throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "rollback()"); 750 } 751 try { 752 status = STATUS_ENDED; 753 connection.rollback(); 754 } catch (SQLException ex) { 755 markWithError(); 756 throw ex; 757 } 758 } 759 760 public void setAutoCommit(boolean autoCommit) throws SQLException { 761 if (status == STATUS_IDLE) { 762 throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "setAutoCommit()"); 763 } 764 try { 765 connection.setAutoCommit(autoCommit); 766 } catch (SQLException ex) { 767 markWithError(); 768 throw ex; 769 } 770 } 771 772 public void setCatalog(String catalog) throws SQLException { 773 if (status == STATUS_IDLE) { 774 throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "setCatalog()"); 775 } 776 connection.setCatalog(catalog); 777 } 778 779 public void setTypeMap(Map<String, Class<?>> map) throws SQLException { 780 if (status == STATUS_IDLE) { 781 throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "setTypeMap()"); 782 } 783 connection.setTypeMap(map); 784 } 785 786 public Savepoint setSavepoint() throws SQLException { 787 try { 788 return connection.setSavepoint(); 789 } catch (SQLException ex) { 790 markWithError(); 791 throw ex; 792 } 793 } 794 795 public Savepoint setSavepoint(String savepointName) throws SQLException { 796 try { 797 return connection.setSavepoint(savepointName); 798 } catch (SQLException ex) { 799 markWithError(); 800 throw ex; 801 } 802 } 803 804 public void rollback(Savepoint sp) throws SQLException { 805 try { 806 connection.rollback(sp); 807 } catch (SQLException ex) { 808 markWithError(); 809 throw ex; 810 } 811 } 812 813 public void releaseSavepoint(Savepoint sp) throws SQLException { 814 try { 815 connection.releaseSavepoint(sp); 816 } catch (SQLException ex) { 817 markWithError(); 818 throw ex; 819 } 820 } 821 822 public void setHoldability(int i) throws SQLException { 823 try { 824 connection.setHoldability(i); 825 } catch (SQLException ex) { 826 markWithError(); 827 throw ex; 828 } 829 } 830 831 public int getHoldability() throws SQLException { 832 try { 833 return connection.getHoldability(); 834 } catch (SQLException ex) { 835 markWithError(); 836 throw ex; 837 } 838 } 839 840 public Statement createStatement(int i, int x, int y) throws SQLException { 841 try { 842 return connection.createStatement(i, x, y); 843 } catch (SQLException ex) { 844 markWithError(); 845 throw ex; 846 } 847 } 848 849 public PreparedStatement prepareStatement(String s, int i, int x, int y) throws SQLException { 850 try { 851 return connection.prepareStatement(s, i, x, y); 852 } catch (SQLException ex) { 853 markWithError(); 854 throw ex; 855 } 856 } 857 858 public PreparedStatement prepareStatement(String s, int[] i) throws SQLException { 859 try { 860 return connection.prepareStatement(s, i); 861 } catch (SQLException ex) { 862 markWithError(); 863 throw ex; 864 } 865 } 866 867 public PreparedStatement prepareStatement(String s, String[] s2) throws SQLException { 868 try { 869 return connection.prepareStatement(s, s2); 870 } catch (SQLException ex) { 871 markWithError(); 872 throw ex; 873 } 874 } 875 876 public CallableStatement prepareCall(String s, int i, int x, int y) throws SQLException { 877 try { 878 return connection.prepareCall(s, i, x, y); 879 } catch (SQLException ex) { 880 markWithError(); 881 throw ex; 882 } 883 } 884 885 /** 886 * Returns the method that created the connection. 887 * <p> 888 * Used to help finding connection pool leaks. 889 * </p> 890 */ 891 private String getCreatedByMethod() { 892 if (createdByMethod != null) { 893 return createdByMethod; 894 } 895 if (stackTrace == null) { 896 return null; 897 } 898 899 for (int j = 0; j < stackTrace.length; j++) { 900 String methodLine = stackTrace[j].toString(); 901 if (!skipElement(methodLine)) { 902 createdByMethod = methodLine; 903 return createdByMethod; 904 } 905 } 906 907 return null; 908 } 909 910 private boolean skipElement(String methodLine) { 911 if (methodLine.startsWith("java.lang.")) { 912 return true; 913 } else if (methodLine.startsWith("java.util.")) { 914 return true; 915 } else if (methodLine.startsWith("org.avaje.datasource.")) { 916 return true; 917 } 918 return methodLine.startsWith("com.avaje.ebean"); 919 } 920 921 /** 922 * Set the stack trace to help find connection pool leaks. 923 */ 924 void setStackTrace(StackTraceElement[] stackTrace) { 925 this.stackTrace = stackTrace; 926 } 927 928 /** 929 * Return the stackTrace as a String for logging purposes. 930 */ 931 private String getStackTraceAsString() { 932 StackTraceElement[] stackTrace = getStackTrace(); 933 if (stackTrace == null) { 934 return ""; 935 } 936 return Arrays.toString(stackTrace); 937 } 938 939 /** 940 * Return the full stack trace that got the connection from the pool. You 941 * could use this if getCreatedByMethod() doesn't work for you. 942 */ 943 private StackTraceElement[] getStackTrace() { 944 945 if (stackTrace == null) { 946 return null; 947 } 948 949 // filter off the top of the stack that we are not interested in 950 ArrayList<StackTraceElement> filteredList = new ArrayList<StackTraceElement>(); 951 boolean include = false; 952 for (int i = 0; i < stackTrace.length; i++) { 953 if (!include && !skipElement(stackTrace[i].toString())) { 954 include = true; 955 } 956 if (include && filteredList.size() < maxStackTrace) { 957 filteredList.add(stackTrace[i]); 958 } 959 } 960 return filteredList.toArray(new StackTraceElement[filteredList.size()]); 961 962 } 963 964}