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 StringBuilder cacheKey = new StringBuilder(sql.length() + 50); 367 cacheKey.append(sql); 368 cacheKey.append(':').append(currentSchema); 369 cacheKey.append(':').append(returnKeysFlag); 370 return prepareStatement(sql, true, returnKeysFlag, cacheKey.toString()); 371 } 372 373 /** 374 * This will try to use a cache of PreparedStatements. 375 */ 376 public PreparedStatement prepareStatement(String sql) throws SQLException { 377 StringBuilder cacheKey = new StringBuilder(sql.length() + 50); 378 cacheKey.append(sql); 379 cacheKey.append(':').append(currentSchema); 380 return prepareStatement(sql, false, 0, cacheKey.toString()); 381 } 382 383 /** 384 * This will try to use a cache of PreparedStatements. 385 */ 386 private PreparedStatement prepareStatement(String sql, boolean useFlag, int flag, String cacheKey) throws SQLException { 387 388 if (status == STATUS_IDLE) { 389 throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "prepareStatement()"); 390 } 391 try { 392 synchronized (pstmtMonitor) { 393 lastStatement = sql; 394 395 // try to get a matching cached PStmt from the cache. 396 ExtendedPreparedStatement pstmt = pstmtCache.remove(cacheKey); 397 398 if (pstmt != null) { 399 return pstmt; 400 } 401 402 // create a new PreparedStatement 403 PreparedStatement actualPstmt; 404 if (useFlag) { 405 actualPstmt = connection.prepareStatement(sql, flag); 406 } else { 407 actualPstmt = connection.prepareStatement(sql); 408 } 409 return new ExtendedPreparedStatement(this, actualPstmt, sql, cacheKey); 410 } 411 412 } catch (SQLException ex) { 413 markWithError(); 414 throw ex; 415 } 416 } 417 418 public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurreny) throws SQLException { 419 420 if (status == STATUS_IDLE) { 421 throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "prepareStatement()"); 422 } 423 try { 424 // no caching when creating PreparedStatements this way 425 lastStatement = sql; 426 return connection.prepareStatement(sql, resultSetType, resultSetConcurreny); 427 } catch (SQLException ex) { 428 markWithError(); 429 throw ex; 430 } 431 } 432 433 /** 434 * Reset the connection for returning to the client. Resets the status, 435 * startUseTime and hadErrors. 436 */ 437 void resetForUse() { 438 this.status = STATUS_ACTIVE; 439 this.startUseTime = System.currentTimeMillis(); 440 this.exeStartNanos = System.nanoTime(); 441 this.createdByMethod = null; 442 this.lastStatement = null; 443 this.hadErrors = false; 444 this.longRunning = false; 445 } 446 447 /** 448 * When an error occurs during use add it the connection. 449 * <p> 450 * Any PooledConnection that has an error is checked to make sure it works 451 * before it is placed back into the connection pool. 452 * </p> 453 */ 454 void markWithError() { 455 hadErrors = true; 456 } 457 458 /** 459 * close the connection putting it back into the connection pool. 460 * <p> 461 * Note that to ensure that the next transaction starts at the correct time 462 * a commit() or rollback() should be called. If neither has occured at this 463 * time then a rollback() is used (to end the transaction). 464 * </p> 465 * <p> 466 * To close the connection fully use closeConnectionFully(). 467 * </p> 468 */ 469 public void close() throws SQLException { 470 if (status == STATUS_IDLE) { 471 throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "close()"); 472 } 473 474 long durationNanos = System.nanoTime() - exeStartNanos; 475 stats.add(durationNanos, hadErrors); 476 477 if (hadErrors) { 478 if (!pool.validateConnection(this)) { 479 // the connection is BAD, remove it, close it and test the pool 480 pool.returnConnectionForceClose(this); 481 return; 482 } 483 } 484 485 try { 486 // reset the autoCommit back if client code changed it 487 if (connection.getAutoCommit() != pool.isAutoCommit()) { 488 connection.setAutoCommit(pool.isAutoCommit()); 489 } 490 // Generally resetting Isolation level seems expensive. 491 // Hence using resetIsolationReadOnlyRequired flag 492 // performance reasons. 493 if (resetIsolationReadOnlyRequired) { 494 resetIsolationReadOnly(); 495 resetIsolationReadOnlyRequired = false; 496 } 497 498 // the connection is assumed GOOD so put it back in the pool 499 lastUseTime = System.currentTimeMillis(); 500 // connection.clearWarnings(); 501 status = STATUS_IDLE; 502 pool.returnConnection(this); 503 504 } catch (Exception ex) { 505 // the connection is BAD, remove it, close it and test the pool 506 logger.warn("Error when trying to return connection to pool, closing fully.", ex); 507 pool.returnConnectionForceClose(this); 508 } 509 } 510 511 private void resetIsolationReadOnly() throws SQLException { 512 // reset the transaction isolation if the client code changed it 513 //noinspection MagicConstant 514 if (connection.getTransactionIsolation() != pool.getTransactionIsolation()) { 515 //noinspection MagicConstant 516 connection.setTransactionIsolation(pool.getTransactionIsolation()); 517 } 518 // reset readonly to false 519 if (connection.isReadOnly()) { 520 connection.setReadOnly(false); 521 } 522 } 523 524 protected void finalize() throws Throwable { 525 try { 526 if (connection != null && !connection.isClosed()) { 527 // connect leak? 528 logger.warn("Closing Connection on finalize() - {}", getFullDescription()); 529 closeConnectionFully(false); 530 } 531 } catch (Exception e) { 532 logger.error("Error when finalize is closing a connection? (unexpected)", e); 533 } 534 super.finalize(); 535 } 536 537 /** 538 * Return true if the connection is too old. 539 */ 540 private boolean exceedsMaxAge(long maxAgeMillis) { 541 if (maxAgeMillis > 0 && (creationTime < (System.currentTimeMillis() - maxAgeMillis))) { 542 this.closeReason = REASON_MAXAGE; 543 return true; 544 } 545 return false; 546 } 547 548 boolean shouldTrimOnReturn(long lastResetTime, long maxAgeMillis) { 549 if (creationTime <= lastResetTime) { 550 this.closeReason = REASON_RESET; 551 return true; 552 } 553 return exceedsMaxAge(maxAgeMillis); 554 } 555 556 /** 557 * Return true if the connection has been idle for too long or is too old. 558 */ 559 boolean shouldTrim(long usedSince, long createdSince) { 560 if (lastUseTime < usedSince) { 561 // been idle for too long so trim it 562 this.closeReason = REASON_IDLE; 563 return true; 564 } 565 if (createdSince > 0 && createdSince > creationTime) { 566 // exceeds max age so trim it 567 this.closeReason = REASON_MAXAGE; 568 return true; 569 } 570 return false; 571 } 572 573 /** 574 * Return the time the connection was passed to the client code. 575 * <p> 576 * Used to detect busy connections that could be leaks. 577 * </p> 578 */ 579 private long getStartUseTime() { 580 return startUseTime; 581 } 582 583 /** 584 * Returns the time the connection was last used. 585 * <p> 586 * Used to close connections that have been idle for some time. Typically 5 587 * minutes. 588 * </p> 589 */ 590 long getLastUsedTime() { 591 return lastUseTime; 592 } 593 594 /** 595 * Returns the last sql statement executed. 596 */ 597 private String getLastStatement() { 598 return lastStatement; 599 } 600 601 /** 602 * Called by ExtendedStatement to trace the sql being executed. 603 * <p> 604 * Note with addBatch() this will not really work. 605 * </p> 606 */ 607 void setLastStatement(String lastStatement) { 608 this.lastStatement = lastStatement; 609 if (logger.isTraceEnabled()) { 610 logger.trace(".setLastStatement[" + lastStatement + "]"); 611 } 612 } 613 614 615 /** 616 * Also note the read only status needs to be reset when put back into the 617 * pool. 618 */ 619 public void setReadOnly(boolean readOnly) throws SQLException { 620 // A bit loose not checking for STATUS_IDLE 621 // if (status == STATUS_IDLE) { 622 // throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + 623 // "setReadOnly()"); 624 // } 625 resetIsolationReadOnlyRequired = true; 626 connection.setReadOnly(readOnly); 627 } 628 629 /** 630 * Also note the Isolation level needs to be reset when put back into the 631 * pool. 632 */ 633 public void setTransactionIsolation(int level) throws SQLException { 634 if (status == STATUS_IDLE) { 635 throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "setTransactionIsolation()"); 636 } 637 try { 638 resetIsolationReadOnlyRequired = true; 639 connection.setTransactionIsolation(level); 640 } catch (SQLException ex) { 641 markWithError(); 642 throw ex; 643 } 644 } 645 646 // 647 // 648 // Simple wrapper methods which pass a method call onto the acutal 649 // connection object. These methods are safe-guarded to prevent use of 650 // the methods whilst the connection is in the connection pool. 651 // 652 // 653 public void clearWarnings() throws SQLException { 654 if (status == STATUS_IDLE) { 655 throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "clearWarnings()"); 656 } 657 connection.clearWarnings(); 658 } 659 660 public void commit() throws SQLException { 661 if (status == STATUS_IDLE) { 662 throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "commit()"); 663 } 664 try { 665 status = STATUS_ENDED; 666 connection.commit(); 667 } catch (SQLException ex) { 668 markWithError(); 669 throw ex; 670 } 671 } 672 673 public boolean getAutoCommit() throws SQLException { 674 if (status == STATUS_IDLE) { 675 throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "getAutoCommit()"); 676 } 677 return connection.getAutoCommit(); 678 } 679 680 public String getCatalog() throws SQLException { 681 if (status == STATUS_IDLE) { 682 throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "getCatalog()"); 683 } 684 return connection.getCatalog(); 685 } 686 687 public DatabaseMetaData getMetaData() throws SQLException { 688 if (status == STATUS_IDLE) { 689 throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "getMetaData()"); 690 } 691 return connection.getMetaData(); 692 } 693 694 public int getTransactionIsolation() throws SQLException { 695 if (status == STATUS_IDLE) { 696 throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "getTransactionIsolation()"); 697 } 698 return connection.getTransactionIsolation(); 699 } 700 701 public Map<String, Class<?>> getTypeMap() throws SQLException { 702 if (status == STATUS_IDLE) { 703 throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "getTypeMap()"); 704 } 705 return connection.getTypeMap(); 706 } 707 708 public SQLWarning getWarnings() throws SQLException { 709 if (status == STATUS_IDLE) { 710 throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "getWarnings()"); 711 } 712 return connection.getWarnings(); 713 } 714 715 public boolean isClosed() throws SQLException { 716 if (status == STATUS_IDLE) { 717 throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "isClosed()"); 718 } 719 return connection.isClosed(); 720 } 721 722 public boolean isReadOnly() throws SQLException { 723 if (status == STATUS_IDLE) { 724 throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "isReadOnly()"); 725 } 726 return connection.isReadOnly(); 727 } 728 729 public String nativeSQL(String sql) throws SQLException { 730 if (status == STATUS_IDLE) { 731 throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "nativeSQL()"); 732 } 733 lastStatement = sql; 734 return connection.nativeSQL(sql); 735 } 736 737 public CallableStatement prepareCall(String sql) throws SQLException { 738 if (status == STATUS_IDLE) { 739 throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "prepareCall()"); 740 } 741 lastStatement = sql; 742 return connection.prepareCall(sql); 743 } 744 745 public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurreny) throws SQLException { 746 if (status == STATUS_IDLE) { 747 throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "prepareCall()"); 748 } 749 lastStatement = sql; 750 return connection.prepareCall(sql, resultSetType, resultSetConcurreny); 751 } 752 753 public void rollback() throws SQLException { 754 if (status == STATUS_IDLE) { 755 throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "rollback()"); 756 } 757 try { 758 status = STATUS_ENDED; 759 connection.rollback(); 760 } catch (SQLException ex) { 761 markWithError(); 762 throw ex; 763 } 764 } 765 766 public void setAutoCommit(boolean autoCommit) throws SQLException { 767 if (status == STATUS_IDLE) { 768 throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "setAutoCommit()"); 769 } 770 try { 771 connection.setAutoCommit(autoCommit); 772 } catch (SQLException ex) { 773 markWithError(); 774 throw ex; 775 } 776 } 777 778 public void setCatalog(String catalog) throws SQLException { 779 if (status == STATUS_IDLE) { 780 throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "setCatalog()"); 781 } 782 connection.setCatalog(catalog); 783 } 784 785 public void setTypeMap(Map<String, Class<?>> map) throws SQLException { 786 if (status == STATUS_IDLE) { 787 throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "setTypeMap()"); 788 } 789 connection.setTypeMap(map); 790 } 791 792 public Savepoint setSavepoint() throws SQLException { 793 try { 794 return connection.setSavepoint(); 795 } catch (SQLException ex) { 796 markWithError(); 797 throw ex; 798 } 799 } 800 801 public Savepoint setSavepoint(String savepointName) throws SQLException { 802 try { 803 return connection.setSavepoint(savepointName); 804 } catch (SQLException ex) { 805 markWithError(); 806 throw ex; 807 } 808 } 809 810 public void rollback(Savepoint sp) throws SQLException { 811 try { 812 connection.rollback(sp); 813 } catch (SQLException ex) { 814 markWithError(); 815 throw ex; 816 } 817 } 818 819 public void releaseSavepoint(Savepoint sp) throws SQLException { 820 try { 821 connection.releaseSavepoint(sp); 822 } catch (SQLException ex) { 823 markWithError(); 824 throw ex; 825 } 826 } 827 828 public void setHoldability(int i) throws SQLException { 829 try { 830 connection.setHoldability(i); 831 } catch (SQLException ex) { 832 markWithError(); 833 throw ex; 834 } 835 } 836 837 public int getHoldability() throws SQLException { 838 try { 839 return connection.getHoldability(); 840 } catch (SQLException ex) { 841 markWithError(); 842 throw ex; 843 } 844 } 845 846 public Statement createStatement(int i, int x, int y) throws SQLException { 847 try { 848 return connection.createStatement(i, x, y); 849 } catch (SQLException ex) { 850 markWithError(); 851 throw ex; 852 } 853 } 854 855 public PreparedStatement prepareStatement(String s, int i, int x, int y) throws SQLException { 856 try { 857 return connection.prepareStatement(s, i, x, y); 858 } catch (SQLException ex) { 859 markWithError(); 860 throw ex; 861 } 862 } 863 864 public PreparedStatement prepareStatement(String s, int[] i) throws SQLException { 865 try { 866 return connection.prepareStatement(s, i); 867 } catch (SQLException ex) { 868 markWithError(); 869 throw ex; 870 } 871 } 872 873 public PreparedStatement prepareStatement(String s, String[] s2) throws SQLException { 874 try { 875 return connection.prepareStatement(s, s2); 876 } catch (SQLException ex) { 877 markWithError(); 878 throw ex; 879 } 880 } 881 882 public CallableStatement prepareCall(String s, int i, int x, int y) throws SQLException { 883 try { 884 return connection.prepareCall(s, i, x, y); 885 } catch (SQLException ex) { 886 markWithError(); 887 throw ex; 888 } 889 } 890 891 /** 892 * Returns the method that created the connection. 893 * <p> 894 * Used to help finding connection pool leaks. 895 * </p> 896 */ 897 private String getCreatedByMethod() { 898 if (createdByMethod != null) { 899 return createdByMethod; 900 } 901 if (stackTrace == null) { 902 return null; 903 } 904 905 for (int j = 0; j < stackTrace.length; j++) { 906 String methodLine = stackTrace[j].toString(); 907 if (!skipElement(methodLine)) { 908 createdByMethod = methodLine; 909 return createdByMethod; 910 } 911 } 912 913 return null; 914 } 915 916 private boolean skipElement(String methodLine) { 917 if (methodLine.startsWith("java.lang.")) { 918 return true; 919 } else if (methodLine.startsWith("java.util.")) { 920 return true; 921 } else if (methodLine.startsWith("org.avaje.datasource.")) { 922 return true; 923 } 924 return methodLine.startsWith("com.avaje.ebean"); 925 } 926 927 /** 928 * Set the stack trace to help find connection pool leaks. 929 */ 930 void setStackTrace(StackTraceElement[] stackTrace) { 931 this.stackTrace = stackTrace; 932 } 933 934 /** 935 * Return the stackTrace as a String for logging purposes. 936 */ 937 private String getStackTraceAsString() { 938 StackTraceElement[] stackTrace = getStackTrace(); 939 if (stackTrace == null) { 940 return ""; 941 } 942 return Arrays.toString(stackTrace); 943 } 944 945 /** 946 * Return the full stack trace that got the connection from the pool. You 947 * could use this if getCreatedByMethod() doesn't work for you. 948 */ 949 private StackTraceElement[] getStackTrace() { 950 951 if (stackTrace == null) { 952 return null; 953 } 954 955 // filter off the top of the stack that we are not interested in 956 ArrayList<StackTraceElement> filteredList = new ArrayList<StackTraceElement>(); 957 boolean include = false; 958 for (int i = 0; i < stackTrace.length; i++) { 959 if (!include && !skipElement(stackTrace[i].toString())) { 960 include = true; 961 } 962 if (include && filteredList.size() < maxStackTrace) { 963 filteredList.add(stackTrace[i]); 964 } 965 } 966 return filteredList.toArray(new StackTraceElement[filteredList.size()]); 967 968 } 969 970}