Skip to content

Commit 152fe0a

Browse files
committed
Rollback/Commit in close if there may be an open transaction
1 parent d672910 commit 152fe0a

File tree

7 files changed

+301
-3
lines changed

7 files changed

+301
-3
lines changed

ebean-datasource-api/src/main/java/io/ebean/datasource/DataSourceBuilder.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -749,6 +749,28 @@ default DataSourceBuilder initDatabaseForPlatform(String platform) {
749749
*/
750750
DataSourceBuilder loadSettings(Properties properties, String poolName);
751751

752+
753+
/**
754+
* Sets the behaviour what to do, when a connection is closed (=returned to pool), while there is uncommitted work.
755+
* <p>
756+
* Possible values
757+
* <ul>
758+
* <li><code>nothing</code> nothing happens</li>
759+
* <li><code>rollback</code> a rollback is performed (default)</li>
760+
* <li><code>commit</code> a commit is performed (dangerous!)</li>
761+
* <li><code>remove</code> the connection is removed from the pool (not recommended for production, connection might leak)</li>
762+
* <li><code>fail</code> an exception is thrown by close itself (for debugging, not recommended for production)</li>
763+
* </ul>
764+
*/
765+
DataSourceBuilder closeWithinTxn(String closeWithinTxn);
766+
767+
/**
768+
* Returns the behaviour, what to do, when a connection is (=returned to pool), while there is uncommitted work.
769+
* <p>
770+
* See {@link #closeWithinTxn(String)}.
771+
*/
772+
String closeWithinTxn();
773+
752774
/**
753775
* The settings of the DataSourceBuilder. Provides getters/accessors
754776
* to read the configured properties of this DataSourceBuilder.

ebean-datasource-api/src/main/java/io/ebean/datasource/DataSourceConfig.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ public class DataSourceConfig implements DataSourceBuilder.Settings {
8383
private String applicationName;
8484
private boolean shutdownOnJvmExit;
8585
private boolean validateOnHeartbeat = !System.getenv().containsKey("LAMBDA_TASK_ROOT");
86+
private String closeWithinTxn = "rollback";
8687

8788
@Override
8889
public Settings settings() {
@@ -144,6 +145,7 @@ public DataSourceConfig copy() {
144145
copy.initSql = initSql;
145146
copy.alert = alert;
146147
copy.listener = listener;
148+
copy.closeWithinTxn = closeWithinTxn;
147149
return copy;
148150
}
149151

@@ -798,6 +800,8 @@ private void loadSettings(ConfigPropertiesHelper properties) {
798800
offline = properties.getBoolean("offline", offline);
799801
shutdownOnJvmExit = properties.getBoolean("shutdownOnJvmExit", shutdownOnJvmExit);
800802
validateOnHeartbeat = properties.getBoolean("validateOnHeartbeat", validateOnHeartbeat);
803+
closeWithinTxn = properties.get("closeWithinTxn", closeWithinTxn);
804+
801805

802806
String isoLevel = properties.get("isolationLevel", _isolationLevel(isolationLevel));
803807
this.isolationLevel = _isolationLevel(isoLevel);
@@ -846,6 +850,17 @@ Map<String, String> parseCustom(String customProperties) {
846850
return propertyMap;
847851
}
848852

853+
@Override
854+
public DataSourceBuilder closeWithinTxn(String closeWithinTxn) {
855+
this.closeWithinTxn = closeWithinTxn;
856+
return this;
857+
}
858+
859+
@Override
860+
public String closeWithinTxn() {
861+
return closeWithinTxn;
862+
}
863+
849864
/**
850865
* Return the isolation level description from the associated Connection int value.
851866
*/

ebean-datasource-api/src/main/java/io/ebean/datasource/DataSourcePoolListener.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,12 @@ public interface DataSourcePoolListener {
1717
/**
1818
* Called after a connection has been retrieved from the connection pool
1919
*/
20-
void onAfterBorrowConnection(Connection connection);
20+
default void onAfterBorrowConnection(Connection connection) {}
2121

2222
/**
2323
* Called before a connection will be put back to the connection pool
2424
*/
25-
void onBeforeReturnConnection(Connection connection);
25+
default void onBeforeReturnConnection(Connection connection) {}
26+
2627

2728
}

ebean-datasource/pom.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,13 @@
7676
<scope>test</scope>
7777
</dependency>
7878

79+
<dependency>
80+
<groupId>com.ibm.db2</groupId>
81+
<artifactId>jcc</artifactId>
82+
<version>11.5.9.0</version>
83+
<scope>test</scope>
84+
</dependency>
85+
7986
</dependencies>
8087

8188
</project>

ebean-datasource/src/main/java/io/ebean/datasource/pool/ConnectionPool.java

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@
2525
*/
2626
final class ConnectionPool implements DataSourcePool {
2727

28+
enum CloseWithinTxn {
29+
NOTHING, ROLLBACK, COMMIT, FAIL, REMOVE;
30+
}
31+
2832
private static final String APPLICATION_NAME = "ApplicationName";
2933
private final ReentrantLock heartbeatLock = new ReentrantLock(false);
3034
private final ReentrantLock notifyLock = new ReentrantLock(false);
@@ -55,6 +59,7 @@ final class ConnectionPool implements DataSourcePool {
5559
private final boolean failOnStart;
5660
private final int maxInactiveMillis;
5761
private final long validateStaleMillis;
62+
private final CloseWithinTxn closeWithinTxn;
5863
/**
5964
* Max age a connection is allowed in millis.
6065
* A value of 0 means no limit (no trimming based on max age).
@@ -94,6 +99,7 @@ final class ConnectionPool implements DataSourcePool {
9499
private final boolean shutdownOnJvmExit;
95100
private Thread shutdownHook;
96101

102+
97103
ConnectionPool(String name, DataSourceConfig params) {
98104
this.config = params;
99105
this.name = name;
@@ -128,10 +134,12 @@ final class ConnectionPool implements DataSourcePool {
128134
this.user = params.getUsername();
129135
this.shutdownOnJvmExit = params.isShutdownOnJvmExit();
130136
this.source = DriverDataSource.of(name, params);
137+
this.closeWithinTxn = Enum.valueOf(CloseWithinTxn.class, params.closeWithinTxn().toUpperCase(Locale.ROOT));
131138
if (!params.isOffline()) {
132139
init();
133140
}
134141
this.nextTrimTime = System.currentTimeMillis() + trimPoolFreqMillis;
142+
135143
}
136144

137145
private void init() {
@@ -516,6 +524,37 @@ boolean invalidConnection(PooledConnection conn) {
516524
}
517525
}
518526

527+
/**
528+
* Fail hard in close() when there is uncommitted work. This is for debugging to find wrong code.
529+
*/
530+
boolean failIfWithinTransaction() {
531+
return closeWithinTxn == CloseWithinTxn.FAIL;
532+
}
533+
534+
/**
535+
* will be called, before the connection is returned to the pool. This happens, when there may
536+
* be uncommitted changes and before all settings like autocommit/schema/catalog are reset to default.
537+
* <p>
538+
* If this method fails, the connection is silently removed from pool
539+
*/
540+
void closeWithinTxn(PooledConnection pooledConnection) throws SQLException {
541+
switch (closeWithinTxn) {
542+
case NOTHING:
543+
Log.trace("Closing active connection.");
544+
break;
545+
case ROLLBACK:
546+
pooledConnection.rollback();
547+
Log.trace("Closing active connection. Rollback performed.");
548+
break;
549+
case COMMIT:
550+
pooledConnection.commit();
551+
Log.trace("Closing active connection. Commit performed.");
552+
break;
553+
case REMOVE:
554+
throw new SQLException("Closing active connection. Removing from pool.");
555+
}
556+
}
557+
519558
/**
520559
* Called by the PooledConnection themselves, returning themselves to the
521560
* pool when they have been finished with.

ebean-datasource/src/main/java/io/ebean/datasource/pool/PooledConnection.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,11 @@ public void close() throws SQLException {
448448
if (status == STATUS_IDLE) {
449449
throw new SQLException(IDLE_CONNECTION_ACCESSED_ERROR + "close()");
450450
}
451+
boolean mayHaveUncommittedChanges = !autoCommit && !readOnly && status == STATUS_ACTIVE;
452+
if (mayHaveUncommittedChanges && pool.failIfWithinTransaction()) {
453+
pool.returnConnectionForceClose(this);
454+
throw new SQLException("Tried to close active connection within transaction");
455+
}
451456
if (hadErrors) {
452457
if (failoverToReadOnly) {
453458
pool.returnConnectionReset(this);
@@ -458,12 +463,14 @@ public void close() throws SQLException {
458463
return;
459464
}
460465
}
461-
462466
try {
463467
if (connection.isClosed()) {
464468
pool.removeClosedConnection(this);
465469
return;
466470
}
471+
if (mayHaveUncommittedChanges) {
472+
pool.closeWithinTxn(this);
473+
}
467474
// reset the autoCommit back if client code changed it
468475
if (autoCommit != pool.isAutoCommit()) {
469476
connection.setAutoCommit(pool.isAutoCommit());

0 commit comments

Comments
 (0)