Skip to content

Commit a644245

Browse files
committed
Check well-known database error codes in case of generic SQL state 23000
Closes gh-29699
1 parent 697292b commit a644245

File tree

6 files changed

+66
-4
lines changed

6 files changed

+66
-4
lines changed

spring-jdbc/src/main/java/org/springframework/jdbc/support/SQLExceptionSubclassTranslator.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ else if (ex instanceof SQLNonTransientException) {
8989
return new DataIntegrityViolationException(buildMessage(task, sql, ex), ex);
9090
}
9191
if (ex instanceof SQLIntegrityConstraintViolationException) {
92-
if ("23505".equals(ex.getSQLState())) {
92+
if (SQLStateSQLExceptionTranslator.indicatesDuplicateKey(ex.getSQLState(), ex.getErrorCode())) {
9393
return new DuplicateKeyException(buildMessage(task, sql, ex), ex);
9494
}
9595
return new DataIntegrityViolationException(buildMessage(task, sql, ex), ex);

spring-jdbc/src/main/java/org/springframework/jdbc/support/SQLStateSQLExceptionTranslator.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ protected DataAccessException doTranslate(String task, @Nullable String sql, SQL
9999
return new BadSqlGrammarException(task, (sql != null ? sql : ""), ex);
100100
}
101101
else if (DATA_INTEGRITY_VIOLATION_CODES.contains(classCode)) {
102-
if ("23505".equals(sqlState)) {
102+
if (indicatesDuplicateKey(sqlState, ex.getErrorCode())) {
103103
return new DuplicateKeyException(buildMessage(task, sql, ex), ex);
104104
}
105105
return new DataIntegrityViolationException(buildMessage(task, sql, ex), ex);
@@ -148,4 +148,19 @@ private String getSqlState(SQLException ex) {
148148
return sqlState;
149149
}
150150

151+
/**
152+
* Check whether the given SQL state (and the associated error code in case
153+
* of a generic SQL state value) indicate a duplicate key exception:
154+
* either SQL state 23505 as a specific indication, or the generic SQL state
155+
* 23000 with well-known vendor codes (1 for Oracle, 1062 for MySQL/MariaDB,
156+
* 2627 for MS SQL Server).
157+
* @param sqlState the SQL state value
158+
* @param errorCode the error code value
159+
*/
160+
static boolean indicatesDuplicateKey(@Nullable String sqlState, int errorCode) {
161+
return ("23505".equals(sqlState) ||
162+
("23000".equals(sqlState) &&
163+
(errorCode == 1 || errorCode == 1062 || errorCode == 2627)));
164+
}
165+
151166
}

spring-jdbc/src/test/java/org/springframework/jdbc/support/SQLExceptionSubclassTranslatorTests.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ public void exceptionClassTranslation() {
5757
doTest(new SQLFeatureNotSupportedException("", "", 0), InvalidDataAccessApiUsageException.class);
5858
doTest(new SQLIntegrityConstraintViolationException("", "", 0), DataIntegrityViolationException.class);
5959
doTest(new SQLIntegrityConstraintViolationException("", "23505", 0), DuplicateKeyException.class);
60+
doTest(new SQLIntegrityConstraintViolationException("", "23000", 1), DuplicateKeyException.class);
61+
doTest(new SQLIntegrityConstraintViolationException("", "23000", 1062), DuplicateKeyException.class);
62+
doTest(new SQLIntegrityConstraintViolationException("", "23505", 2627), DuplicateKeyException.class);
6063
doTest(new SQLInvalidAuthorizationSpecException("", "", 0), PermissionDeniedDataAccessException.class);
6164
doTest(new SQLNonTransientConnectionException("", "", 0), DataAccessResourceFailureException.class);
6265
doTest(new SQLRecoverableException("", "", 0), RecoverableDataAccessException.class);

spring-jdbc/src/test/java/org/springframework/jdbc/support/SQLStateSQLExceptionTranslatorTests.java

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,21 @@ public void translateDuplicateKey() {
6161
doTest("23505", DuplicateKeyException.class);
6262
}
6363

64+
@Test
65+
public void translateDuplicateKeyOracle() {
66+
doTest("23000", 1, DuplicateKeyException.class);
67+
}
68+
69+
@Test
70+
public void translateDuplicateKeyMySQL() {
71+
doTest("23000", 1062, DuplicateKeyException.class);
72+
}
73+
74+
@Test
75+
public void translateDuplicateKeyMSSQL() {
76+
doTest("23000", 2627, DuplicateKeyException.class);
77+
}
78+
6479
@Test
6580
public void translateDataAccessResourceFailure() {
6681
doTest("53", DataAccessResourceFailureException.class);
@@ -105,8 +120,12 @@ public void malformedSqlStateCodes() {
105120

106121

107122
private void doTest(@Nullable String sqlState, @Nullable Class<?> dataAccessExceptionType) {
123+
doTest(sqlState, 0, dataAccessExceptionType);
124+
}
125+
126+
private void doTest(@Nullable String sqlState, int errorCode, @Nullable Class<?> dataAccessExceptionType) {
108127
SQLExceptionTranslator translator = new SQLStateSQLExceptionTranslator();
109-
SQLException ex = new SQLException("reason", sqlState);
128+
SQLException ex = new SQLException("reason", sqlState, errorCode);
110129
DataAccessException dax = translator.translate("task", "SQL", ex);
111130

112131
if (dataAccessExceptionType == null) {

spring-r2dbc/src/main/java/org/springframework/r2dbc/connection/ConnectionFactoryUtils.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ else if (ex instanceof R2dbcNonTransientException) {
231231
return new DataAccessResourceFailureException(buildMessage(task, sql, ex), ex);
232232
}
233233
if (ex instanceof R2dbcDataIntegrityViolationException) {
234-
if ("23505".equals(ex.getSqlState())) {
234+
if (indicatesDuplicateKey(ex.getSqlState(), ex.getErrorCode())) {
235235
return new DuplicateKeyException(buildMessage(task, sql, ex), ex);
236236
}
237237
return new DataIntegrityViolationException(buildMessage(task, sql, ex), ex);
@@ -246,6 +246,19 @@ else if (ex instanceof R2dbcNonTransientException) {
246246
return new UncategorizedR2dbcException(buildMessage(task, sql, ex), sql, ex);
247247
}
248248

249+
/**
250+
* Check whether the given SQL state (and the associated error code in case
251+
* of a generic SQL state value) indicate a duplicate key exception. See
252+
* {@code org.springframework.jdbc.support.SQLStateSQLExceptionTranslator#indicatesDuplicateKey}.
253+
* @param sqlState the SQL state value
254+
* @param errorCode the error code value
255+
*/
256+
static boolean indicatesDuplicateKey(@Nullable String sqlState, int errorCode) {
257+
return ("23505".equals(sqlState) ||
258+
("23000".equals(sqlState) &&
259+
(errorCode == 1 || errorCode == 1062 || errorCode == 2627)));
260+
}
261+
249262
/**
250263
* Build a message {@code String} for the given {@link R2dbcException}.
251264
* <p>To be called by translator subclasses when creating an instance of a generic

spring-r2dbc/src/test/java/org/springframework/r2dbc/connection/ConnectionFactoryUtilsUnitTests.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,18 @@ public void shouldTranslateIntegrityViolationException() {
9595
exception = ConnectionFactoryUtils.convertR2dbcException("", "",
9696
new R2dbcDataIntegrityViolationException("reason", "23505"));
9797
assertThat(exception).isExactlyInstanceOf(DuplicateKeyException.class);
98+
99+
exception = ConnectionFactoryUtils.convertR2dbcException("", "",
100+
new R2dbcDataIntegrityViolationException("reason", "23000", 1));
101+
assertThat(exception).isExactlyInstanceOf(DuplicateKeyException.class);
102+
103+
exception = ConnectionFactoryUtils.convertR2dbcException("", "",
104+
new R2dbcDataIntegrityViolationException("reason", "23000", 1062));
105+
assertThat(exception).isExactlyInstanceOf(DuplicateKeyException.class);
106+
107+
exception = ConnectionFactoryUtils.convertR2dbcException("", "",
108+
new R2dbcDataIntegrityViolationException("reason", "23000", 2627));
109+
assertThat(exception).isExactlyInstanceOf(DuplicateKeyException.class);
98110
}
99111

100112
@Test

0 commit comments

Comments
 (0)