Check well-known database error codes in case of generic SQL state 23000
Closes gh-29699
This commit is contained in:
parent
697292ba0c
commit
a644245e0e
|
@ -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);
|
||||||
|
|
|
@ -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)));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue