#2 - Introduce R2DBC exception translation.

We now provide exception translation for R2DBC exceptions based on Spring JDBC's SQLErrorCodes.
This commit is contained in:
Mark Paluch
2018-06-22 11:12:00 +02:00
parent 368835c128
commit 831e753658
14 changed files with 1128 additions and 56 deletions

View File

@@ -25,6 +25,7 @@ import reactor.test.StepVerifier;
import org.junit.Before;
import org.junit.Test;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.jdbc.core.mapping.Table;
@@ -75,6 +76,27 @@ public class DatabaseClientIntegrationTests extends R2dbcIntegrationTestSupport
assertThat(jdbc.queryForMap("SELECT id, name, manual FROM legoset")).containsEntry("id", 42055);
}
@Test
public void shouldTranslateDuplicateKeyException() {
DatabaseClient databaseClient = DatabaseClient.create(connectionFactory);
executeInsert();
databaseClient.execute().sql("INSERT INTO legoset (id, name, manual) VALUES($1, $2, $3)") //
.bind(0, 42055) //
.bind(1, "SCHAUFELRADBAGGER") //
.bindNull("$3") //
.fetch().rowsUpdated() //
.as(StepVerifier::create) //
.expectErrorSatisfies(exception -> {
assertThat(exception).isInstanceOf(DuplicateKeyException.class)
.hasMessageContaining("execute; SQL [INSERT INTO legoset");
}) //
.verify();
}
@Test
public void executeSelect() {

View File

@@ -0,0 +1,158 @@
/*
* Copyright 2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.r2dbc.support;
import static org.assertj.core.api.Assertions.*;
import io.r2dbc.spi.R2dbcException;
import org.junit.Test;
import org.springframework.dao.CannotAcquireLockException;
import org.springframework.dao.CannotSerializeTransactionException;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.DataAccessResourceFailureException;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.dao.DeadlockLoserDataAccessException;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.data.r2dbc.BadSqlGrammarException;
import org.springframework.data.r2dbc.InvalidResultAccessException;
import org.springframework.data.r2dbc.UncategorizedR2dbcException;
import org.springframework.jdbc.support.SQLErrorCodes;
/**
* Unit tests for {@link SqlErrorCodeR2dbcExceptionTranslator}.
*
* @author Mark Paluch
*/
public class SqlErrorCodeR2dbcExceptionTranslatorUnitTests {
private static SQLErrorCodes ERROR_CODES = new SQLErrorCodes();
static {
ERROR_CODES.setBadSqlGrammarCodes("1", "2");
ERROR_CODES.setInvalidResultSetAccessCodes("3", "4");
ERROR_CODES.setDuplicateKeyCodes("10");
ERROR_CODES.setDataAccessResourceFailureCodes("5");
ERROR_CODES.setDataIntegrityViolationCodes("6");
ERROR_CODES.setCannotAcquireLockCodes("7");
ERROR_CODES.setDeadlockLoserCodes("8");
ERROR_CODES.setCannotSerializeTransactionCodes("9");
}
@Test
public void shouldTranslateToBadGrammarException() {
R2dbcExceptionTranslator sut = new SqlErrorCodeR2dbcExceptionTranslator(ERROR_CODES);
R2dbcException cause = new MyR2dbcException("", "", 1);
BadSqlGrammarException exception = (BadSqlGrammarException) sut.translate("task", "SQL", cause);
assertThat(exception.getSql()).isEqualTo("SQL");
assertThat(exception.getR2dbcException()).isEqualTo(cause);
}
@Test
public void shouldTranslateToResultException() {
R2dbcExceptionTranslator sut = new SqlErrorCodeR2dbcExceptionTranslator(ERROR_CODES);
R2dbcException cause = new MyR2dbcException("", "", 4);
InvalidResultAccessException exception = (InvalidResultAccessException) sut.translate("task", "SQL", cause);
assertThat(exception.getSql()).isEqualTo("SQL");
assertThat(exception.getR2dbcException()).isEqualTo(cause);
}
@Test
public void shouldFallbackToUncategorized() {
R2dbcExceptionTranslator sut = new SqlErrorCodeR2dbcExceptionTranslator(ERROR_CODES);
// Test fallback. We assume that no database will ever return this error code,
// but 07xxx will be bad grammar picked up by the fallback SQLState translator
R2dbcException cause = new MyR2dbcException("", "07xxx", 666666666);
UncategorizedR2dbcException exception = (UncategorizedR2dbcException) sut.translate("task", "SQL2", cause);
assertThat(exception.getSql()).isEqualTo("SQL2");
assertThat(exception.getR2dbcException()).isEqualTo(cause);
}
@Test
public void shouldTranslateDataIntegrityViolationException() {
R2dbcExceptionTranslator sut = new SqlErrorCodeR2dbcExceptionTranslator(ERROR_CODES);
R2dbcException cause = new MyR2dbcException("", "", 10);
DataAccessException exception = sut.translate("task", "SQL", cause);
assertThat(exception).isInstanceOf(DataIntegrityViolationException.class);
}
@Test
public void errorCodeTranslation() {
R2dbcExceptionTranslator sut = new SqlErrorCodeR2dbcExceptionTranslator(ERROR_CODES);
checkTranslation(sut, 5, DataAccessResourceFailureException.class);
checkTranslation(sut, 6, DataIntegrityViolationException.class);
checkTranslation(sut, 7, CannotAcquireLockException.class);
checkTranslation(sut, 8, DeadlockLoserDataAccessException.class);
checkTranslation(sut, 9, CannotSerializeTransactionException.class);
checkTranslation(sut, 10, DuplicateKeyException.class);
}
private static void checkTranslation(R2dbcExceptionTranslator sext, int errorCode, Class<?> exClass) {
R2dbcException cause = new MyR2dbcException("", "", errorCode);
DataAccessException exception = sext.translate("", "", cause);
assertThat(exception).isInstanceOf(exClass).hasCause(cause);
}
@Test
public void shouldApplyCustomTranslation() {
String TASK = "TASK";
String SQL = "SQL SELECT *";
DataAccessException custom = new DataAccessException("") {};
R2dbcException cause = new MyR2dbcException("", "", 1);
R2dbcException intVioEx = new MyR2dbcException("", "", 6);
SqlErrorCodeR2dbcExceptionTranslator translator = new SqlErrorCodeR2dbcExceptionTranslator() {
@Override
protected DataAccessException customTranslate(String task, String sql, R2dbcException sqlex) {
assertThat(task).isEqualTo(TASK);
assertThat(sql).isEqualTo(SQL);
return (sqlex == cause) ? custom : null;
}
};
translator.setSqlErrorCodes(ERROR_CODES);
// Shouldn't custom translate this
assertThat(translator.translate(TASK, SQL, cause)).isEqualTo(custom);
DataIntegrityViolationException diex = (DataIntegrityViolationException) translator.translate(TASK, SQL, intVioEx);
assertThat(diex).hasCause(intVioEx);
}
static class MyR2dbcException extends R2dbcException {
MyR2dbcException(String reason, String sqlState, int errorCode) {
super(reason, sqlState, errorCode);
}
}
}

View File

@@ -0,0 +1,85 @@
/*
* Copyright 2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.r2dbc.support;
import static org.assertj.core.api.Assertions.*;
import io.r2dbc.spi.R2dbcException;
import org.junit.Test;
import org.springframework.dao.ConcurrencyFailureException;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.DataAccessResourceFailureException;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.dao.TransientDataAccessResourceException;
import org.springframework.data.r2dbc.BadSqlGrammarException;
import org.springframework.data.r2dbc.UncategorizedR2dbcException;
/**
* Unit tests for {@link SqlStateR2dbcExceptionTranslator}.
*
* @author Mark Paluch
*/
public class SqlStateR2dbcExceptionTranslatorUnitTests {
private static final String REASON = "The game is afoot!";
private static final String TASK = "Counting sheep... yawn.";
private static final String SQL = "select count(0) from t_sheep where over_fence = ... yawn... 1";
@Test(expected = IllegalArgumentException.class)
public void testTranslateNullException() {
new SqlStateR2dbcExceptionTranslator().translate("", "", null);
}
@Test
public void testTranslateBadSqlGrammar() {
doTest("07", BadSqlGrammarException.class);
}
@Test
public void testTranslateDataIntegrityViolation() {
doTest("23", DataIntegrityViolationException.class);
}
@Test
public void testTranslateDataAccessResourceFailure() {
doTest("53", DataAccessResourceFailureException.class);
}
@Test
public void testTranslateTransientDataAccessResourceFailure() {
doTest("S1", TransientDataAccessResourceException.class);
}
@Test
public void testTranslateConcurrencyFailure() {
doTest("40", ConcurrencyFailureException.class);
}
@Test
public void testTranslateUncategorized() {
doTest("00000000", UncategorizedR2dbcException.class);
}
private static void doTest(String sqlState, Class<?> dataAccessExceptionType) {
R2dbcException ex = new R2dbcException(REASON, sqlState) {};
SqlStateR2dbcExceptionTranslator translator = new SqlStateR2dbcExceptionTranslator();
DataAccessException dax = translator.translate(TASK, SQL, ex);
assertThat(dax).isNotNull().isInstanceOf(dataAccessExceptionType).hasCause(ex);
}
}