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 f912f54cc95..4b962d02ecb 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 ac05ddb4ab5..94abeccca32 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);