Introduce rollbackBeforeClose property and AutoCloseable implementation
Closes gh-27249
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user