From e33adadc62d6851e664aa5ec366c5a8448a68e03 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Wed, 18 Jun 2025 12:20:09 +0200 Subject: [PATCH 1/3] Detect SQL state 57014 as QueryTimeoutException Closes gh-35073 --- .../SQLStateSQLExceptionTranslator.java | 34 +++++++++++++------ .../SQLStateSQLExceptionTranslatorTests.java | 6 ++++ 2 files changed, 29 insertions(+), 11 deletions(-) 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 f912f54cc9..4b962d02ec 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 @@ -70,6 +70,11 @@ public class SQLStateSQLExceptionTranslator extends AbstractFallbackSQLException "44" // With check violation ); + private static final Set PESSIMISTIC_LOCKING_FAILURE_CODES = Set.of( + "40", // Transaction rollback + "61" // Oracle: deadlock + ); + private static final Set DATA_ACCESS_RESOURCE_FAILURE_CODES = Set.of( "08", // Connection exception "53", // PostgreSQL: insufficient resources (for example, disk full) @@ -84,11 +89,6 @@ public class SQLStateSQLExceptionTranslator extends AbstractFallbackSQLException "S1" // DB2: communication failure ); - private static final Set PESSIMISTIC_LOCKING_FAILURE_CODES = Set.of( - "40", // Transaction rollback - "61" // Oracle: deadlock - ); - private static final Set DUPLICATE_KEY_ERROR_CODES = Set.of( 1, // Oracle 301, // SAP HANA @@ -117,18 +117,21 @@ public class SQLStateSQLExceptionTranslator extends AbstractFallbackSQLException } return new DataIntegrityViolationException(buildMessage(task, sql, ex), ex); } - else if (DATA_ACCESS_RESOURCE_FAILURE_CODES.contains(classCode)) { - return new DataAccessResourceFailureException(buildMessage(task, sql, ex), ex); - } - else if (TRANSIENT_DATA_ACCESS_RESOURCE_CODES.contains(classCode)) { - return new TransientDataAccessResourceException(buildMessage(task, sql, ex), ex); - } else if (PESSIMISTIC_LOCKING_FAILURE_CODES.contains(classCode)) { if (indicatesCannotAcquireLock(sqlState)) { return new CannotAcquireLockException(buildMessage(task, sql, ex), ex); } return new PessimisticLockingFailureException(buildMessage(task, sql, ex), ex); } + else if (DATA_ACCESS_RESOURCE_FAILURE_CODES.contains(classCode)) { + if (indicatesQueryTimeout(sqlState)) { + return new QueryTimeoutException(buildMessage(task, sql, ex), ex); + } + return new DataAccessResourceFailureException(buildMessage(task, sql, ex), ex); + } + else if (TRANSIENT_DATA_ACCESS_RESOURCE_CODES.contains(classCode)) { + return new TransientDataAccessResourceException(buildMessage(task, sql, ex), ex); + } } // For MySQL: exception class name indicating a timeout? @@ -184,4 +187,13 @@ public class SQLStateSQLExceptionTranslator extends AbstractFallbackSQLException return "40001".equals(sqlState); } + /** + * Check whether the given SQL state indicates a {@link QueryTimeoutException}, + * with SQL state 57014 as a specific indication. + * @param sqlState the SQL state value + */ + static boolean indicatesQueryTimeout(@Nullable String sqlState) { + return "57014".equals(sqlState); + } + } 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 ac05ddb4ab..94abeccca3 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 @@ -26,6 +26,7 @@ import org.springframework.dao.DataAccessResourceFailureException; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.dao.DuplicateKeyException; import org.springframework.dao.PessimisticLockingFailureException; +import org.springframework.dao.QueryTimeoutException; import org.springframework.dao.TransientDataAccessResourceException; import org.springframework.jdbc.BadSqlGrammarException; import org.springframework.lang.Nullable; @@ -109,6 +110,11 @@ class SQLStateSQLExceptionTranslatorTests { assertTranslation("40001", CannotAcquireLockException.class); } + @Test + void translateQueryTimeout() { + assertTranslation("57014", QueryTimeoutException.class); + } + @Test void translateUncategorized() { assertTranslation("00000000", null); From 00399dc1b3bf49c7c79d2258f2d171edba99b50e Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Wed, 18 Jun 2025 12:20:19 +0200 Subject: [PATCH 2/3] Reject null for returned CompletableFuture (avoiding subsequent NPE) See gh-34708 --- .../springframework/cache/interceptor/CacheAspectSupport.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheAspectSupport.java b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheAspectSupport.java index c1097f9f72..bb3ae01bdf 100644 --- a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheAspectSupport.java +++ b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheAspectSupport.java @@ -463,7 +463,7 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker () -> { CompletableFuture invokeResult = ((CompletableFuture) invokeOperation(invoker)); if (invokeResult == null) { - return null; + throw new IllegalStateException("Returned CompletableFuture must not be null: " + method); } return invokeResult.exceptionallyCompose(ex -> { invokeFailure.set(true); From aa525cc3cde3c4ecc2067e743bbabd948e7ce7e4 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Wed, 18 Jun 2025 12:20:30 +0200 Subject: [PATCH 3/3] Delegate to ReflectionUtils.accessibleConstructor --- .../annotation/ReflectiveRuntimeHintsRegistrar.java | 10 +++++----- .../core/io/support/PropertySourceProcessor.java | 5 +---- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/spring-core/src/main/java/org/springframework/aot/hint/annotation/ReflectiveRuntimeHintsRegistrar.java b/spring-core/src/main/java/org/springframework/aot/hint/annotation/ReflectiveRuntimeHintsRegistrar.java index b57ac1828c..565c35182d 100644 --- a/spring-core/src/main/java/org/springframework/aot/hint/annotation/ReflectiveRuntimeHintsRegistrar.java +++ b/spring-core/src/main/java/org/springframework/aot/hint/annotation/ReflectiveRuntimeHintsRegistrar.java @@ -130,15 +130,14 @@ public class ReflectiveRuntimeHintsRegistrar { private ReflectiveProcessor instantiateClass(Class type) { try { - Constructor constructor = type.getDeclaredConstructor(); - ReflectionUtils.makeAccessible(constructor); - return constructor.newInstance(); + return ReflectionUtils.accessibleConstructor(type).newInstance(); } catch (Exception ex) { throw new IllegalStateException("Failed to instantiate " + type, ex); } } + private static class DelegatingReflectiveProcessor implements ReflectiveProcessor { private final Iterable processors; @@ -151,9 +150,10 @@ public class ReflectiveRuntimeHintsRegistrar { public void registerReflectionHints(ReflectionHints hints, AnnotatedElement element) { this.processors.forEach(processor -> processor.registerReflectionHints(hints, element)); } - } - private record Entry(AnnotatedElement element, ReflectiveProcessor processor) {} + + private record Entry(AnnotatedElement element, ReflectiveProcessor processor) { + } } diff --git a/spring-core/src/main/java/org/springframework/core/io/support/PropertySourceProcessor.java b/spring-core/src/main/java/org/springframework/core/io/support/PropertySourceProcessor.java index 9e27a0df64..55436e0f34 100644 --- a/spring-core/src/main/java/org/springframework/core/io/support/PropertySourceProcessor.java +++ b/spring-core/src/main/java/org/springframework/core/io/support/PropertySourceProcessor.java @@ -18,7 +18,6 @@ package org.springframework.core.io.support; import java.io.FileNotFoundException; import java.io.IOException; -import java.lang.reflect.Constructor; import java.net.SocketException; import java.net.UnknownHostException; import java.util.ArrayList; @@ -147,9 +146,7 @@ public class PropertySourceProcessor { private static PropertySourceFactory instantiateClass(Class type) { try { - Constructor constructor = type.getDeclaredConstructor(); - ReflectionUtils.makeAccessible(constructor); - return constructor.newInstance(); + return ReflectionUtils.accessibleConstructor(type).newInstance(); } catch (Exception ex) { throw new IllegalStateException("Failed to instantiate " + type, ex);