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