Skip to content

Commit e161476

Browse files
mp911deschauder
authored andcommitted
#57 - Add support for R2DBC subclass exception translation.
We now use R2DBC's exception hierarchy to translate exceptions into Spring's DataAccessException hierarchy. Original pull request: #97.
1 parent 9c6142f commit e161476

File tree

7 files changed

+220
-9
lines changed

7 files changed

+220
-9
lines changed

pom.xml

+2-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
<mysql.version>5.1.47</mysql.version>
3535
<jasync.version>0.9.38</jasync.version>
3636
<mssql-jdbc.version>7.1.2.jre8-preview</mssql-jdbc.version>
37-
<r2dbc-releasetrain.version>Arabba-M7</r2dbc-releasetrain.version>
37+
<r2dbc-releasetrain.version>Arabba-BUILD-SNAPSHOT</r2dbc-releasetrain.version>
3838
<reactive-streams.version>1.0.1</reactive-streams.version>
3939
<testcontainers.version>1.10.1</testcontainers.version>
4040

@@ -111,6 +111,7 @@
111111
<dependency>
112112
<groupId>org.springframework</groupId>
113113
<artifactId>spring-jdbc</artifactId>
114+
<optional>true</optional>
114115
</dependency>
115116

116117
<dependency>

src/main/java/org/springframework/data/r2dbc/config/AbstractR2dbcConfiguration.java

+5-2
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,9 @@
3535
import org.springframework.data.r2dbc.function.ReactiveDataAccessStrategy;
3636
import org.springframework.data.r2dbc.function.convert.MappingR2dbcConverter;
3737
import org.springframework.data.r2dbc.function.convert.R2dbcCustomConversions;
38+
import org.springframework.data.r2dbc.support.R2dbcExceptionSubclassTranslator;
3839
import org.springframework.data.r2dbc.support.R2dbcExceptionTranslator;
39-
import org.springframework.data.r2dbc.support.SqlErrorCodeR2dbcExceptionTranslator;
40+
import org.springframework.data.r2dbc.support.SqlStateR2dbcExceptionTranslator;
4041
import org.springframework.data.relational.core.conversion.BasicRelationalConverter;
4142
import org.springframework.data.relational.core.mapping.NamingStrategy;
4243
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
@@ -186,10 +187,12 @@ protected StoreConversions getStoreConversions() {
186187
*
187188
* @return must not be {@literal null}.
188189
* @see #connectionFactory()
190+
* @see R2dbcExceptionSubclassTranslator
191+
* @see SqlStateR2dbcExceptionTranslator
189192
*/
190193
@Bean
191194
public R2dbcExceptionTranslator exceptionTranslator() {
192-
return new SqlErrorCodeR2dbcExceptionTranslator(lookupConnectionFactory());
195+
return new R2dbcExceptionSubclassTranslator();
193196
}
194197

195198
ConnectionFactory lookupConnectionFactory() {

src/main/java/org/springframework/data/r2dbc/function/DefaultDatabaseClientBuilder.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@
2323
import org.springframework.data.r2dbc.dialect.Database;
2424
import org.springframework.data.r2dbc.dialect.Dialect;
2525
import org.springframework.data.r2dbc.function.DatabaseClient.Builder;
26+
import org.springframework.data.r2dbc.support.R2dbcExceptionSubclassTranslator;
2627
import org.springframework.data.r2dbc.support.R2dbcExceptionTranslator;
27-
import org.springframework.data.r2dbc.support.SqlErrorCodeR2dbcExceptionTranslator;
2828
import org.springframework.lang.Nullable;
2929
import org.springframework.util.Assert;
3030

@@ -114,7 +114,7 @@ public DatabaseClient build() {
114114
R2dbcExceptionTranslator exceptionTranslator = this.exceptionTranslator;
115115

116116
if (exceptionTranslator == null) {
117-
exceptionTranslator = new SqlErrorCodeR2dbcExceptionTranslator(connectionFactory);
117+
exceptionTranslator = new R2dbcExceptionSubclassTranslator();
118118
}
119119

120120
ReactiveDataAccessStrategy accessStrategy = this.accessStrategy;

src/main/java/org/springframework/data/r2dbc/support/AbstractFallbackR2dbcExceptionTranslator.java

+3-2
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
import org.apache.commons.logging.Log;
2121
import org.apache.commons.logging.LogFactory;
22+
2223
import org.springframework.dao.DataAccessException;
2324
import org.springframework.data.r2dbc.UncategorizedR2dbcException;
2425
import org.springframework.lang.NonNull;
@@ -99,7 +100,7 @@ public DataAccessException translate(String task, @Nullable String sql, R2dbcExc
99100
protected abstract DataAccessException doTranslate(String task, @Nullable String sql, R2dbcException ex);
100101

101102
/**
102-
* Build a message {@code String} for the given {@link java.sql.R2dbcException}.
103+
* Build a message {@code String} for the given {@link R2dbcException}.
103104
* <p>
104105
* To be called by translator subclasses when creating an instance of a generic
105106
* {@link org.springframework.dao.DataAccessException} class.
@@ -110,6 +111,6 @@ public DataAccessException translate(String task, @Nullable String sql, R2dbcExc
110111
* @return the message {@code String} to use.
111112
*/
112113
protected String buildMessage(String task, @Nullable String sql, R2dbcException ex) {
113-
return task + "; " + (sql != null ? "SQL [" + sql : "]; " + "") + ex.getMessage();
114+
return task + "; " + (sql != null ? "SQL [" + sql + "]; " : "") + ex.getMessage();
114115
}
115116
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/*
2+
* Copyright 2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.r2dbc.support;
17+
18+
import io.r2dbc.spi.R2dbcBadGrammarException;
19+
import io.r2dbc.spi.R2dbcDataIntegrityViolationException;
20+
import io.r2dbc.spi.R2dbcException;
21+
import io.r2dbc.spi.R2dbcNonTransientException;
22+
import io.r2dbc.spi.R2dbcNonTransientResourceException;
23+
import io.r2dbc.spi.R2dbcPermissionDeniedException;
24+
import io.r2dbc.spi.R2dbcRollbackException;
25+
import io.r2dbc.spi.R2dbcTimeoutException;
26+
import io.r2dbc.spi.R2dbcTransientException;
27+
import io.r2dbc.spi.R2dbcTransientResourceException;
28+
29+
import org.springframework.dao.ConcurrencyFailureException;
30+
import org.springframework.dao.DataAccessException;
31+
import org.springframework.dao.DataAccessResourceFailureException;
32+
import org.springframework.dao.DataIntegrityViolationException;
33+
import org.springframework.dao.PermissionDeniedDataAccessException;
34+
import org.springframework.dao.QueryTimeoutException;
35+
import org.springframework.dao.TransientDataAccessResourceException;
36+
import org.springframework.data.r2dbc.BadSqlGrammarException;
37+
import org.springframework.lang.Nullable;
38+
39+
/**
40+
* {@link R2dbcExceptionTranslator} implementation which analyzes the specific {@link R2dbcException} subclass thrown by
41+
* the R2DBC driver.
42+
* <p>
43+
* Falls back to a standard {@link SqlStateR2dbcExceptionTranslator}.
44+
*
45+
* @author Mark Paluch
46+
*/
47+
public class R2dbcExceptionSubclassTranslator extends AbstractFallbackR2dbcExceptionTranslator {
48+
49+
public R2dbcExceptionSubclassTranslator() {
50+
setFallbackTranslator(new SqlStateR2dbcExceptionTranslator());
51+
}
52+
53+
/*
54+
* (non-Javadoc)
55+
* @see org.springframework.data.r2dbc.support.AbstractFallbackR2dbcExceptionTranslator#doTranslate(java.lang.String, java.lang.String, io.r2dbc.spi.R2dbcException)
56+
*/
57+
@Override
58+
@Nullable
59+
protected DataAccessException doTranslate(String task, @Nullable String sql, R2dbcException ex) {
60+
61+
if (ex instanceof R2dbcTransientException) {
62+
if (ex instanceof R2dbcTransientResourceException) {
63+
return new TransientDataAccessResourceException(buildMessage(task, sql, ex), ex);
64+
}
65+
if (ex instanceof R2dbcRollbackException) {
66+
return new ConcurrencyFailureException(buildMessage(task, sql, ex), ex);
67+
}
68+
if (ex instanceof R2dbcTimeoutException) {
69+
return new QueryTimeoutException(buildMessage(task, sql, ex), ex);
70+
}
71+
}
72+
73+
if (ex instanceof R2dbcNonTransientException) {
74+
if (ex instanceof R2dbcNonTransientResourceException) {
75+
return new DataAccessResourceFailureException(buildMessage(task, sql, ex), ex);
76+
}
77+
if (ex instanceof R2dbcDataIntegrityViolationException) {
78+
return new DataIntegrityViolationException(buildMessage(task, sql, ex), ex);
79+
}
80+
if (ex instanceof R2dbcPermissionDeniedException) {
81+
return new PermissionDeniedDataAccessException(buildMessage(task, sql, ex), ex);
82+
}
83+
if (ex instanceof R2dbcBadGrammarException) {
84+
return new BadSqlGrammarException(task, (sql != null ? sql : ""), ex);
85+
}
86+
}
87+
88+
// Fallback to Spring's own R2DBC state translation...
89+
return null;
90+
}
91+
}

src/test/java/org/springframework/data/r2dbc/function/AbstractDatabaseClientIntegrationTests.java

+3-2
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,9 @@
2727

2828
import org.junit.Before;
2929
import org.junit.Test;
30+
3031
import org.springframework.dao.DataAccessException;
31-
import org.springframework.dao.DuplicateKeyException;
32+
import org.springframework.dao.DataIntegrityViolationException;
3233
import org.springframework.data.domain.PageRequest;
3334
import org.springframework.data.domain.Sort;
3435
import org.springframework.data.r2dbc.testing.R2dbcIntegrationTestSupport;
@@ -125,7 +126,7 @@ public void shouldTranslateDuplicateKeyException() {
125126
.fetch().rowsUpdated() //
126127
.as(StepVerifier::create) //
127128
.expectErrorSatisfies(exception -> assertThat(exception) //
128-
.isInstanceOf(DuplicateKeyException.class) //
129+
.isInstanceOf(DataIntegrityViolationException.class) //
129130
.hasMessageContaining("execute; SQL [INSERT INTO legoset")) //
130131
.verify();
131132
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
/*
2+
* Copyright 2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.r2dbc.support;
17+
18+
import static org.assertj.core.api.Assertions.*;
19+
20+
import io.r2dbc.spi.R2dbcBadGrammarException;
21+
import io.r2dbc.spi.R2dbcDataIntegrityViolationException;
22+
import io.r2dbc.spi.R2dbcException;
23+
import io.r2dbc.spi.R2dbcNonTransientResourceException;
24+
import io.r2dbc.spi.R2dbcPermissionDeniedException;
25+
import io.r2dbc.spi.R2dbcRollbackException;
26+
import io.r2dbc.spi.R2dbcTimeoutException;
27+
import io.r2dbc.spi.R2dbcTransientResourceException;
28+
29+
import org.junit.Test;
30+
31+
import org.springframework.dao.ConcurrencyFailureException;
32+
import org.springframework.dao.DataAccessResourceFailureException;
33+
import org.springframework.dao.DataIntegrityViolationException;
34+
import org.springframework.dao.PermissionDeniedDataAccessException;
35+
import org.springframework.dao.QueryTimeoutException;
36+
import org.springframework.dao.TransientDataAccessResourceException;
37+
import org.springframework.data.r2dbc.BadSqlGrammarException;
38+
import org.springframework.data.r2dbc.UncategorizedR2dbcException;
39+
40+
/**
41+
* Unit tests for {@link R2dbcExceptionSubclassTranslator}.
42+
*
43+
* @author Mark Paluch
44+
*/
45+
public class R2dbcExceptionSubclassTranslatorUnitTests {
46+
47+
R2dbcExceptionSubclassTranslator translator = new R2dbcExceptionSubclassTranslator();
48+
49+
@Test // gh-57
50+
public void shouldTranslateTransientResourceException() {
51+
52+
Exception exception = translator.translate("", "", new R2dbcTransientResourceException());
53+
54+
assertThat(exception).isInstanceOf(TransientDataAccessResourceException.class);
55+
}
56+
57+
@Test // gh-57
58+
public void shouldTranslateRollbackException() {
59+
60+
Exception exception = translator.translate("", "", new R2dbcRollbackException());
61+
62+
assertThat(exception).isInstanceOf(ConcurrencyFailureException.class);
63+
}
64+
65+
@Test // gh-57
66+
public void shouldTranslateTimeoutException() {
67+
68+
Exception exception = translator.translate("", "", new R2dbcTimeoutException());
69+
70+
assertThat(exception).isInstanceOf(QueryTimeoutException.class);
71+
}
72+
73+
@Test // gh-57
74+
public void shouldNotTranslateUnknownExceptions() {
75+
76+
Exception exception = translator.translate("", "", new MyTransientExceptions());
77+
78+
assertThat(exception).isInstanceOf(UncategorizedR2dbcException.class);
79+
}
80+
81+
@Test // gh-57
82+
public void shouldTranslateNonTransientResourceException() {
83+
84+
Exception exception = translator.translate("", "", new R2dbcNonTransientResourceException());
85+
86+
assertThat(exception).isInstanceOf(DataAccessResourceFailureException.class);
87+
}
88+
89+
@Test // gh-57
90+
public void shouldTranslateIntegrityViolationException() {
91+
92+
Exception exception = translator.translate("", "", new R2dbcDataIntegrityViolationException());
93+
94+
assertThat(exception).isInstanceOf(DataIntegrityViolationException.class);
95+
}
96+
97+
@Test // gh-57
98+
public void shouldTranslatePermissionDeniedException() {
99+
100+
Exception exception = translator.translate("", "", new R2dbcPermissionDeniedException());
101+
102+
assertThat(exception).isInstanceOf(PermissionDeniedDataAccessException.class);
103+
}
104+
105+
@Test // gh-57
106+
public void shouldTranslateBadSqlGrammarException() {
107+
108+
Exception exception = translator.translate("", "", new R2dbcBadGrammarException());
109+
110+
assertThat(exception).isInstanceOf(BadSqlGrammarException.class);
111+
}
112+
113+
private static class MyTransientExceptions extends R2dbcException {}
114+
}

0 commit comments

Comments
 (0)