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