Check well-known database error codes in case of generic SQL state 23000

Closes gh-29699
This commit is contained in:
Juergen Hoeller 2022-12-23 15:13:29 +01:00
parent 697292ba0c
commit a644245e0e
6 changed files with 66 additions and 4 deletions

View File

@ -89,7 +89,7 @@ public class SQLExceptionSubclassTranslator extends AbstractFallbackSQLException
return new DataIntegrityViolationException(buildMessage(task, sql, ex), ex); return new DataIntegrityViolationException(buildMessage(task, sql, ex), ex);
} }
if (ex instanceof SQLIntegrityConstraintViolationException) { if (ex instanceof SQLIntegrityConstraintViolationException) {
if ("23505".equals(ex.getSQLState())) { if (SQLStateSQLExceptionTranslator.indicatesDuplicateKey(ex.getSQLState(), ex.getErrorCode())) {
return new DuplicateKeyException(buildMessage(task, sql, ex), ex); return new DuplicateKeyException(buildMessage(task, sql, ex), ex);
} }
return new DataIntegrityViolationException(buildMessage(task, sql, ex), ex); return new DataIntegrityViolationException(buildMessage(task, sql, ex), ex);

View File

@ -99,7 +99,7 @@ public class SQLStateSQLExceptionTranslator extends AbstractFallbackSQLException
return new BadSqlGrammarException(task, (sql != null ? sql : ""), ex); return new BadSqlGrammarException(task, (sql != null ? sql : ""), ex);
} }
else if (DATA_INTEGRITY_VIOLATION_CODES.contains(classCode)) { else if (DATA_INTEGRITY_VIOLATION_CODES.contains(classCode)) {
if ("23505".equals(sqlState)) { if (indicatesDuplicateKey(sqlState, ex.getErrorCode())) {
return new DuplicateKeyException(buildMessage(task, sql, ex), ex); return new DuplicateKeyException(buildMessage(task, sql, ex), ex);
} }
return new DataIntegrityViolationException(buildMessage(task, sql, ex), ex); return new DataIntegrityViolationException(buildMessage(task, sql, ex), ex);
@ -148,4 +148,19 @@ public class SQLStateSQLExceptionTranslator extends AbstractFallbackSQLException
return sqlState; return sqlState;
} }
/**
* Check whether the given SQL state (and the associated error code in case
* of a generic SQL state value) indicate a duplicate key exception:
* either SQL state 23505 as a specific indication, or the generic SQL state
* 23000 with well-known vendor codes (1 for Oracle, 1062 for MySQL/MariaDB,
* 2627 for MS SQL Server).
* @param sqlState the SQL state value
* @param errorCode the error code value
*/
static boolean indicatesDuplicateKey(@Nullable String sqlState, int errorCode) {
return ("23505".equals(sqlState) ||
("23000".equals(sqlState) &&
(errorCode == 1 || errorCode == 1062 || errorCode == 2627)));
}
} }

View File

@ -57,6 +57,9 @@ public class SQLExceptionSubclassTranslatorTests {
doTest(new SQLFeatureNotSupportedException("", "", 0), InvalidDataAccessApiUsageException.class); doTest(new SQLFeatureNotSupportedException("", "", 0), InvalidDataAccessApiUsageException.class);
doTest(new SQLIntegrityConstraintViolationException("", "", 0), DataIntegrityViolationException.class); doTest(new SQLIntegrityConstraintViolationException("", "", 0), DataIntegrityViolationException.class);
doTest(new SQLIntegrityConstraintViolationException("", "23505", 0), DuplicateKeyException.class); doTest(new SQLIntegrityConstraintViolationException("", "23505", 0), DuplicateKeyException.class);
doTest(new SQLIntegrityConstraintViolationException("", "23000", 1), DuplicateKeyException.class);
doTest(new SQLIntegrityConstraintViolationException("", "23000", 1062), DuplicateKeyException.class);
doTest(new SQLIntegrityConstraintViolationException("", "23505", 2627), DuplicateKeyException.class);
doTest(new SQLInvalidAuthorizationSpecException("", "", 0), PermissionDeniedDataAccessException.class); doTest(new SQLInvalidAuthorizationSpecException("", "", 0), PermissionDeniedDataAccessException.class);
doTest(new SQLNonTransientConnectionException("", "", 0), DataAccessResourceFailureException.class); doTest(new SQLNonTransientConnectionException("", "", 0), DataAccessResourceFailureException.class);
doTest(new SQLRecoverableException("", "", 0), RecoverableDataAccessException.class); doTest(new SQLRecoverableException("", "", 0), RecoverableDataAccessException.class);

View File

@ -61,6 +61,21 @@ public class SQLStateSQLExceptionTranslatorTests {
doTest("23505", DuplicateKeyException.class); doTest("23505", DuplicateKeyException.class);
} }
@Test
public void translateDuplicateKeyOracle() {
doTest("23000", 1, DuplicateKeyException.class);
}
@Test
public void translateDuplicateKeyMySQL() {
doTest("23000", 1062, DuplicateKeyException.class);
}
@Test
public void translateDuplicateKeyMSSQL() {
doTest("23000", 2627, DuplicateKeyException.class);
}
@Test @Test
public void translateDataAccessResourceFailure() { public void translateDataAccessResourceFailure() {
doTest("53", DataAccessResourceFailureException.class); doTest("53", DataAccessResourceFailureException.class);
@ -105,8 +120,12 @@ public class SQLStateSQLExceptionTranslatorTests {
private void doTest(@Nullable String sqlState, @Nullable Class<?> dataAccessExceptionType) { private void doTest(@Nullable String sqlState, @Nullable Class<?> dataAccessExceptionType) {
doTest(sqlState, 0, dataAccessExceptionType);
}
private void doTest(@Nullable String sqlState, int errorCode, @Nullable Class<?> dataAccessExceptionType) {
SQLExceptionTranslator translator = new SQLStateSQLExceptionTranslator(); SQLExceptionTranslator translator = new SQLStateSQLExceptionTranslator();
SQLException ex = new SQLException("reason", sqlState); SQLException ex = new SQLException("reason", sqlState, errorCode);
DataAccessException dax = translator.translate("task", "SQL", ex); DataAccessException dax = translator.translate("task", "SQL", ex);
if (dataAccessExceptionType == null) { if (dataAccessExceptionType == null) {

View File

@ -231,7 +231,7 @@ public abstract class ConnectionFactoryUtils {
return new DataAccessResourceFailureException(buildMessage(task, sql, ex), ex); return new DataAccessResourceFailureException(buildMessage(task, sql, ex), ex);
} }
if (ex instanceof R2dbcDataIntegrityViolationException) { if (ex instanceof R2dbcDataIntegrityViolationException) {
if ("23505".equals(ex.getSqlState())) { if (indicatesDuplicateKey(ex.getSqlState(), ex.getErrorCode())) {
return new DuplicateKeyException(buildMessage(task, sql, ex), ex); return new DuplicateKeyException(buildMessage(task, sql, ex), ex);
} }
return new DataIntegrityViolationException(buildMessage(task, sql, ex), ex); return new DataIntegrityViolationException(buildMessage(task, sql, ex), ex);
@ -246,6 +246,19 @@ public abstract class ConnectionFactoryUtils {
return new UncategorizedR2dbcException(buildMessage(task, sql, ex), sql, ex); return new UncategorizedR2dbcException(buildMessage(task, sql, ex), sql, ex);
} }
/**
* Check whether the given SQL state (and the associated error code in case
* of a generic SQL state value) indicate a duplicate key exception. See
* {@code org.springframework.jdbc.support.SQLStateSQLExceptionTranslator#indicatesDuplicateKey}.
* @param sqlState the SQL state value
* @param errorCode the error code value
*/
static boolean indicatesDuplicateKey(@Nullable String sqlState, int errorCode) {
return ("23505".equals(sqlState) ||
("23000".equals(sqlState) &&
(errorCode == 1 || errorCode == 1062 || errorCode == 2627)));
}
/** /**
* Build a message {@code String} for the given {@link R2dbcException}. * Build a message {@code String} for the given {@link R2dbcException}.
* <p>To be called by translator subclasses when creating an instance of a generic * <p>To be called by translator subclasses when creating an instance of a generic

View File

@ -95,6 +95,18 @@ public class ConnectionFactoryUtilsUnitTests {
exception = ConnectionFactoryUtils.convertR2dbcException("", "", exception = ConnectionFactoryUtils.convertR2dbcException("", "",
new R2dbcDataIntegrityViolationException("reason", "23505")); new R2dbcDataIntegrityViolationException("reason", "23505"));
assertThat(exception).isExactlyInstanceOf(DuplicateKeyException.class); assertThat(exception).isExactlyInstanceOf(DuplicateKeyException.class);
exception = ConnectionFactoryUtils.convertR2dbcException("", "",
new R2dbcDataIntegrityViolationException("reason", "23000", 1));
assertThat(exception).isExactlyInstanceOf(DuplicateKeyException.class);
exception = ConnectionFactoryUtils.convertR2dbcException("", "",
new R2dbcDataIntegrityViolationException("reason", "23000", 1062));
assertThat(exception).isExactlyInstanceOf(DuplicateKeyException.class);
exception = ConnectionFactoryUtils.convertR2dbcException("", "",
new R2dbcDataIntegrityViolationException("reason", "23000", 2627));
assertThat(exception).isExactlyInstanceOf(DuplicateKeyException.class);
} }
@Test @Test