Introduce rollbackBeforeClose property and AutoCloseable implementation

Closes gh-27249
This commit is contained in:
Juergen Hoeller
2023-11-30 16:36:32 +01:00
parent c56c304536
commit 5f9702b2a4
2 changed files with 166 additions and 16 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -52,12 +52,16 @@ import org.springframework.util.ObjectUtils;
* @see java.sql.Connection#close()
* @see DataSourceUtils#releaseConnection
*/
public class SingleConnectionDataSource extends DriverManagerDataSource implements SmartDataSource, DisposableBean {
public class SingleConnectionDataSource extends DriverManagerDataSource
implements SmartDataSource, AutoCloseable, DisposableBean {
/** Create a close-suppressing proxy?. */
/** Create a close-suppressing proxy? */
private boolean suppressClose;
/** Override auto-commit state?. */
/** Explicit rollback before close? */
private boolean rollbackBeforeClose;
/** Override auto-commit state? */
@Nullable
private Boolean autoCommit;
@@ -124,7 +128,7 @@ public class SingleConnectionDataSource extends DriverManagerDataSource implemen
/**
* Set whether the returned Connection should be a close-suppressing proxy
* Specify whether the returned Connection should be a close-suppressing proxy
* or the physical Connection.
*/
public void setSuppressClose(boolean suppressClose) {
@@ -140,10 +144,29 @@ public class SingleConnectionDataSource extends DriverManagerDataSource implemen
}
/**
* Set whether the returned Connection's "autoCommit" setting should be overridden.
* Specify whether the shared Connection should be explicitly rolled back
* before close (if not in auto-commit mode).
* <p>This is recommended for the Oracle JDBC driver in testing scenarios.
* @since 6.1.2
*/
public void setRollbackBeforeClose(boolean rollbackBeforeClose) {
this.rollbackBeforeClose = rollbackBeforeClose;
}
/**
* Return whether the shared Connection should be explicitly rolled back
* before close (if not in auto-commit mode).
* @since 6.1.2
*/
protected boolean isRollbackBeforeClose() {
return this.rollbackBeforeClose;
}
/**
* Specify whether the returned Connection's "autoCommit" setting should be overridden.
*/
public void setAutoCommit(boolean autoCommit) {
this.autoCommit = (autoCommit);
this.autoCommit = autoCommit;
}
/**
@@ -201,13 +224,27 @@ public class SingleConnectionDataSource extends DriverManagerDataSource implemen
/**
* Close the underlying Connection.
* The provider of this DataSource needs to care for proper shutdown.
* <p>As this bean implements DisposableBean, a bean factory will
* automatically invoke this on destruction of its cached singletons.
* <p>As this class implements {@link AutoCloseable}, it can be used
* with a try-with-resource statement.
* @since 6.1.2
*/
@Override
public void close() {
destroy();
}
/**
* Close the underlying Connection.
* The provider of this DataSource needs to care for proper shutdown.
* <p>As this bean implements {@link DisposableBean}, a bean factory
* will automatically invoke this on destruction of the bean.
*/
@Override
public void destroy() {
synchronized (this.connectionMonitor) {
closeConnection();
if (this.target != null) {
closeConnection(this.target);
}
}
}
@@ -220,7 +257,9 @@ public class SingleConnectionDataSource extends DriverManagerDataSource implemen
throw new IllegalStateException("'url' property is required for lazily initializing a Connection");
}
synchronized (this.connectionMonitor) {
closeConnection();
if (this.target != null) {
closeConnection(this.target);
}
this.target = getConnectionFromDriver(getUsername(), getPassword());
prepareConnection(this.target);
if (logger.isDebugEnabled()) {
@@ -235,7 +274,9 @@ public class SingleConnectionDataSource extends DriverManagerDataSource implemen
*/
public void resetConnection() {
synchronized (this.connectionMonitor) {
closeConnection();
if (this.target != null) {
closeConnection(this.target);
}
this.target = null;
this.connection = null;
}
@@ -257,16 +298,25 @@ public class SingleConnectionDataSource extends DriverManagerDataSource implemen
/**
* Close the underlying shared Connection.
* @since 6.1.2
*/
private void closeConnection() {
if (this.target != null) {
protected void closeConnection(Connection con) {
if (isRollbackBeforeClose()) {
try {
this.target.close();
if (!con.getAutoCommit()) {
con.rollback();
}
}
catch (Throwable ex) {
logger.info("Could not close shared JDBC Connection", ex);
logger.info("Could not roll back shared JDBC Connection before close", ex);
}
}
try {
con.close();
}
catch (Throwable ex) {
logger.info("Could not close shared JDBC Connection", ex);
}
}
/**

View File

@@ -0,0 +1,100 @@
/*
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.jdbc.datasource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
/**
* @author Juergen Hoeller
* @since 6.1.2
*/
public class SingleConnectionDataSourceTests {
private final Connection connection = mock();
@Test
public void plainConnection() throws Exception {
SingleConnectionDataSource ds = new SingleConnectionDataSource(connection, false);
ds.getConnection().close();
verify(connection, times(1)).close();
}
@Test
public void withAutoCloseable() throws Exception {
try (SingleConnectionDataSource ds = new SingleConnectionDataSource(connection, false)) {
ds.getConnection();
}
verify(connection, times(1)).close();
}
@Test
public void withSuppressClose() throws Exception {
SingleConnectionDataSource ds = new SingleConnectionDataSource(connection, true);
ds.getConnection().close();
verify(connection, never()).close();
ds.destroy();
verify(connection, times(1)).close();
given(connection.isClosed()).willReturn(true);
assertThatExceptionOfType(SQLException.class).isThrownBy(ds::getConnection);
}
@Test
public void withRollbackBeforeClose() throws Exception {
SingleConnectionDataSource ds = new SingleConnectionDataSource(connection, true);
ds.setRollbackBeforeClose(true);
ds.destroy();
verify(connection, times(1)).rollback();
verify(connection, times(1)).close();
}
@Test
public void withEnforcedAutoCommit() throws Exception {
SingleConnectionDataSource ds = new SingleConnectionDataSource() {
@Override
protected Connection getConnectionFromDriverManager(String url, Properties props) {
return connection;
}
};
ds.setUrl("url");
ds.setAutoCommit(true);
ds.getConnection();
verify(connection, times(1)).setAutoCommit(true);
ds.destroy();
verify(connection, times(1)).close();
}
}