Commit in DatabasePopulatorUtils if Connection has auto-commit=false

Prior to this commit, DatabasePopulatorUtils.execute(...) did not
perform a commit for the current Connection. This works for most use
cases; however, when DatabasePopulatorUtils is used to execute
initialization scripts without a managed transaction -- for example,
via a DataSourceInitializer configured as a bean in the
ApplicationContext or via Spring Boot configuration in
application.properties -- if the underlying database is configured with
auto-commit=false, the results of executing the SQL scripts are not
committed to the database which can lead to data being silently lost.

This commit addresses this issue by committing the Connection for the
supplied DataSource if the connection is not configured for auto-commit
and is not transactional. Existing use cases running with a managed
transaction should therefore not be affected by this change.

Closes gh-27008
This commit is contained in:
Sam Brannen
2021-10-13 19:09:53 +02:00
parent 4dac8339ff
commit 89c7797ffb
6 changed files with 177 additions and 59 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 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.
@@ -72,7 +72,8 @@ public abstract class DataSourceUtils {
* @return a JDBC Connection from the given DataSource
* @throws org.springframework.jdbc.CannotGetJdbcConnectionException
* if the attempt to get a Connection failed
* @see #releaseConnection
* @see #releaseConnection(Connection, DataSource)
* @see #isConnectionTransactional(Connection, DataSource)
*/
public static Connection getConnection(DataSource dataSource) throws CannotGetJdbcConnectionException {
try {
@@ -298,6 +299,7 @@ public abstract class DataSourceUtils {
* @param dataSource the DataSource that the Connection was obtained from
* (may be {@code null})
* @return whether the Connection is transactional
* @see #getConnection(DataSource)
*/
public static boolean isConnectionTransactional(Connection con, @Nullable DataSource dataSource) {
if (dataSource == null) {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 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.
@@ -36,9 +36,14 @@ public abstract class DatabasePopulatorUtils {
/**
* Execute the given {@link DatabasePopulator} against the given {@link DataSource}.
* <p>As of Spring Framework 5.3.11, the {@link Connection} for the supplied
* {@code DataSource} will be {@linkplain Connection#commit() committed} if
* it is not configured for {@link Connection#getAutoCommit() auto-commit} and
* is not {@linkplain DataSourceUtils#isConnectionTransactional transactional}.
* @param populator the {@code DatabasePopulator} to execute
* @param dataSource the {@code DataSource} to execute against
* @throws DataAccessException if an error occurs, specifically a {@link ScriptException}
* @see DataSourceUtils#isConnectionTransactional(Connection, DataSource)
*/
public static void execute(DatabasePopulator populator, DataSource dataSource) throws DataAccessException {
Assert.notNull(populator, "DatabasePopulator must not be null");
@@ -47,6 +52,9 @@ public abstract class DatabasePopulatorUtils {
Connection connection = DataSourceUtils.getConnection(dataSource);
try {
populator.populate(connection);
if (!connection.getAutoCommit() && !DataSourceUtils.isConnectionTransactional(connection, dataSource)) {
connection.commit();
}
}
finally {
DataSourceUtils.releaseConnection(connection, dataSource);