From 5471961f061b8cb78e4f12c8c6c07e5391726dc6 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Thu, 30 Oct 2025 14:59:00 +0100 Subject: [PATCH] Restore exposure of original BatchUpdateException as root cause Closes gh-35717 See gh-35547 --- .../SQLExceptionSubclassTranslator.java | 24 +++++++++---------- .../SQLStateSQLExceptionTranslator.java | 18 +++++++------- .../SQLStateSQLExceptionTranslatorTests.java | 3 +-- 3 files changed, 22 insertions(+), 23 deletions(-) diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/support/SQLExceptionSubclassTranslator.java b/spring-jdbc/src/main/java/org/springframework/jdbc/support/SQLExceptionSubclassTranslator.java index 00d0a6c8a51..ec2ead52fa9 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/support/SQLExceptionSubclassTranslator.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/support/SQLExceptionSubclassTranslator.java @@ -80,43 +80,43 @@ public class SQLExceptionSubclassTranslator extends AbstractFallbackSQLException if (sqlEx instanceof SQLTransientException) { if (sqlEx instanceof SQLTransientConnectionException) { - return new TransientDataAccessResourceException(buildMessage(task, sql, sqlEx), sqlEx); + return new TransientDataAccessResourceException(buildMessage(task, sql, sqlEx), ex); } if (sqlEx instanceof SQLTransactionRollbackException) { if (SQLStateSQLExceptionTranslator.indicatesCannotAcquireLock(sqlEx.getSQLState())) { - return new CannotAcquireLockException(buildMessage(task, sql, sqlEx), sqlEx); + return new CannotAcquireLockException(buildMessage(task, sql, sqlEx), ex); } - return new PessimisticLockingFailureException(buildMessage(task, sql, sqlEx), sqlEx); + return new PessimisticLockingFailureException(buildMessage(task, sql, sqlEx), ex); } if (sqlEx instanceof SQLTimeoutException) { - return new QueryTimeoutException(buildMessage(task, sql, sqlEx), sqlEx); + return new QueryTimeoutException(buildMessage(task, sql, sqlEx), ex); } } else if (sqlEx instanceof SQLNonTransientException) { if (sqlEx instanceof SQLNonTransientConnectionException) { - return new DataAccessResourceFailureException(buildMessage(task, sql, sqlEx), sqlEx); + return new DataAccessResourceFailureException(buildMessage(task, sql, sqlEx), ex); } if (sqlEx instanceof SQLDataException) { - return new DataIntegrityViolationException(buildMessage(task, sql, sqlEx), sqlEx); + return new DataIntegrityViolationException(buildMessage(task, sql, sqlEx), ex); } if (sqlEx instanceof SQLIntegrityConstraintViolationException) { if (SQLStateSQLExceptionTranslator.indicatesDuplicateKey(sqlEx.getSQLState(), sqlEx.getErrorCode())) { - return new DuplicateKeyException(buildMessage(task, sql, sqlEx), sqlEx); + return new DuplicateKeyException(buildMessage(task, sql, sqlEx), ex); } - return new DataIntegrityViolationException(buildMessage(task, sql, sqlEx), sqlEx); + return new DataIntegrityViolationException(buildMessage(task, sql, sqlEx), ex); } if (sqlEx instanceof SQLInvalidAuthorizationSpecException) { - return new PermissionDeniedDataAccessException(buildMessage(task, sql, sqlEx), sqlEx); + return new PermissionDeniedDataAccessException(buildMessage(task, sql, sqlEx), ex); } if (sqlEx instanceof SQLSyntaxErrorException) { - return new BadSqlGrammarException(task, (sql != null ? sql : ""), sqlEx); + return new BadSqlGrammarException(task, (sql != null ? sql : ""), ex); } if (sqlEx instanceof SQLFeatureNotSupportedException) { - return new InvalidDataAccessApiUsageException(buildMessage(task, sql, sqlEx), sqlEx); + return new InvalidDataAccessApiUsageException(buildMessage(task, sql, sqlEx), ex); } } else if (sqlEx instanceof SQLRecoverableException) { - return new RecoverableDataAccessException(buildMessage(task, sql, sqlEx), sqlEx); + return new RecoverableDataAccessException(buildMessage(task, sql, sqlEx), ex); } // Fallback to Spring's own SQL state translation... diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/support/SQLStateSQLExceptionTranslator.java b/spring-jdbc/src/main/java/org/springframework/jdbc/support/SQLStateSQLExceptionTranslator.java index a7d18f8a9f0..bcdbb0e5de5 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/support/SQLStateSQLExceptionTranslator.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/support/SQLStateSQLExceptionTranslator.java @@ -131,35 +131,35 @@ public class SQLStateSQLExceptionTranslator extends AbstractFallbackSQLException logger.debug("Extracted SQL state class '" + classCode + "' from value '" + sqlState + "'"); } if (BAD_SQL_GRAMMAR_CODES.contains(classCode)) { - return new BadSqlGrammarException(task, (sql != null ? sql : ""), sqlEx); + return new BadSqlGrammarException(task, (sql != null ? sql : ""), ex); } else if (DATA_INTEGRITY_VIOLATION_CODES.contains(classCode)) { if (indicatesDuplicateKey(sqlState, sqlEx.getErrorCode())) { - return new DuplicateKeyException(buildMessage(task, sql, sqlEx), sqlEx); + return new DuplicateKeyException(buildMessage(task, sql, sqlEx), ex); } - return new DataIntegrityViolationException(buildMessage(task, sql, sqlEx), sqlEx); + return new DataIntegrityViolationException(buildMessage(task, sql, sqlEx), ex); } else if (PESSIMISTIC_LOCKING_FAILURE_CODES.contains(classCode)) { if (indicatesCannotAcquireLock(sqlState)) { - return new CannotAcquireLockException(buildMessage(task, sql, sqlEx), sqlEx); + return new CannotAcquireLockException(buildMessage(task, sql, sqlEx), ex); } - return new PessimisticLockingFailureException(buildMessage(task, sql, sqlEx), sqlEx); + return new PessimisticLockingFailureException(buildMessage(task, sql, sqlEx), ex); } else if (DATA_ACCESS_RESOURCE_FAILURE_CODES.contains(classCode)) { if (indicatesQueryTimeout(sqlState)) { - return new QueryTimeoutException(buildMessage(task, sql, sqlEx), sqlEx); + return new QueryTimeoutException(buildMessage(task, sql, sqlEx), ex); } - return new DataAccessResourceFailureException(buildMessage(task, sql, sqlEx), sqlEx); + return new DataAccessResourceFailureException(buildMessage(task, sql, sqlEx), ex); } else if (TRANSIENT_DATA_ACCESS_RESOURCE_CODES.contains(classCode)) { - return new TransientDataAccessResourceException(buildMessage(task, sql, sqlEx), sqlEx); + return new TransientDataAccessResourceException(buildMessage(task, sql, sqlEx), ex); } } // For MySQL: exception class name indicating a timeout? // (since MySQL doesn't throw the JDBC 4 SQLTimeoutException) if (sqlEx.getClass().getName().contains("Timeout")) { - return new QueryTimeoutException(buildMessage(task, sql, sqlEx), sqlEx); + return new QueryTimeoutException(buildMessage(task, sql, sqlEx), ex); } // Couldn't resolve anything proper - resort to UncategorizedSQLException. diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/support/SQLStateSQLExceptionTranslatorTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/support/SQLStateSQLExceptionTranslatorTests.java index fded7c4bedc..49f1b548a3a 100644 --- a/spring-jdbc/src/test/java/org/springframework/jdbc/support/SQLStateSQLExceptionTranslatorTests.java +++ b/spring-jdbc/src/test/java/org/springframework/jdbc/support/SQLStateSQLExceptionTranslatorTests.java @@ -181,8 +181,7 @@ class SQLStateSQLExceptionTranslatorTests { static void assertTranslation(DataAccessException dae, SQLException ex, Class dataAccessExceptionType) { assertThat(dae).as("Specific translation must not result in null").isNotNull(); assertThat(dae).as("Wrong DataAccessException type returned").isExactlyInstanceOf(dataAccessExceptionType); - assertThat(dae.getCause()).as("The exact same original SQLException must be preserved").isSameAs( - ex instanceof BatchUpdateException bue ? bue.getNextException() : ex); + assertThat(dae.getCause()).as("The exact same original SQLException must be preserved").isSameAs(ex); } static BatchUpdateException buildBatchUpdateException(@Nullable String sqlState, SQLException next) {