Skip to content

Commit 6532a21

Browse files
committed
GH-611 added JdbcExceptionTranslator
Signed-off-by: mipo256 <[email protected]>
1 parent 5c0ebf3 commit 6532a21

12 files changed

+220
-23
lines changed

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/AggregateChangeExecutor.java

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,16 @@
1515
*/
1616
package org.springframework.data.jdbc.core;
1717

18-
import org.springframework.dao.OptimisticLockingFailureException;
18+
import java.util.List;
19+
import java.util.Optional;
20+
import org.springframework.dao.support.PersistenceExceptionTranslator;
1921
import org.springframework.data.jdbc.core.convert.DataAccessStrategy;
2022
import org.springframework.data.jdbc.core.convert.JdbcConverter;
2123
import org.springframework.data.relational.core.conversion.AggregateChange;
2224
import org.springframework.data.relational.core.conversion.DbAction;
2325
import org.springframework.data.relational.core.conversion.DbActionExecutionException;
2426
import org.springframework.data.relational.core.conversion.MutableAggregateChange;
2527

26-
import java.util.List;
27-
2828
/**
2929
* Executes an {@link MutableAggregateChange}.
3030
*
@@ -37,11 +37,13 @@ class AggregateChangeExecutor {
3737

3838
private final JdbcConverter converter;
3939
private final DataAccessStrategy accessStrategy;
40+
private final PersistenceExceptionTranslator jdbcExceptionTranslator;
4041

4142
AggregateChangeExecutor(JdbcConverter converter, DataAccessStrategy accessStrategy) {
4243

4344
this.converter = converter;
4445
this.accessStrategy = accessStrategy;
46+
this.jdbcExceptionTranslator = new JdbcExceptionTranslator();
4547
}
4648

4749
/**
@@ -110,12 +112,15 @@ private void execute(DbAction<?> action, JdbcAggregateChangeExecutionContext exe
110112
} else {
111113
throw new RuntimeException("unexpected action");
112114
}
113-
} catch (Exception e) {
114-
115-
if (e instanceof OptimisticLockingFailureException) {
116-
throw e;
117-
}
118-
throw new DbActionExecutionException(action, e);
115+
} catch (RuntimeException e) {
116+
117+
throw Optional
118+
.ofNullable(jdbcExceptionTranslator.translateExceptionIfPossible(e))
119+
.map(it -> (RuntimeException) it)
120+
.orElseGet(() -> {
121+
e.addSuppressed(new DbActionExecutionException(action, e));
122+
return e;
123+
});
119124
}
120125
}
121126
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package org.springframework.data.jdbc.core;
2+
3+
import java.util.Set;
4+
import org.springframework.dao.DataAccessException;
5+
import org.springframework.dao.DuplicateKeyException;
6+
import org.springframework.dao.OptimisticLockingFailureException;
7+
import org.springframework.dao.support.PersistenceExceptionTranslator;
8+
9+
/**
10+
* {@link PersistenceExceptionTranslator} that is capable to translate exceptions for JDBC module.
11+
*
12+
* @author Mikhail Polivakha
13+
*/
14+
public class JdbcExceptionTranslator implements PersistenceExceptionTranslator {
15+
16+
private static final Set<Class<? extends DataAccessException>> PASS_THROUGH_EXCEPTIONS = Set.of(
17+
OptimisticLockingFailureException.class,
18+
DuplicateKeyException.class
19+
);
20+
21+
@Override
22+
public DataAccessException translateExceptionIfPossible(RuntimeException ex) {
23+
if (PASS_THROUGH_EXCEPTIONS.contains(ex.getClass())) {
24+
return (DataAccessException) ex;
25+
}
26+
27+
return null;
28+
}
29+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package org.springframework.data.jdbc.core;
2+
3+
import java.util.stream.Stream;
4+
import org.assertj.core.api.Assertions;
5+
import org.junit.jupiter.api.Test;
6+
import org.junit.jupiter.params.ParameterizedTest;
7+
import org.junit.jupiter.params.provider.Arguments;
8+
import org.junit.jupiter.params.provider.MethodSource;
9+
import org.springframework.dao.DataAccessException;
10+
import org.springframework.dao.DuplicateKeyException;
11+
import org.springframework.dao.OptimisticLockingFailureException;
12+
13+
/**
14+
* Unit tests for {@link JdbcExceptionTranslator}
15+
*
16+
* @author Mikhail Polivakha
17+
*/
18+
class JdbcExceptionTranslatorTest {
19+
20+
@ParameterizedTest
21+
@MethodSource(value = "passThroughExceptionsSource")
22+
void testPassThroughExceptions(DataAccessException exception) {
23+
24+
// when
25+
DataAccessException translated = new JdbcExceptionTranslator().translateExceptionIfPossible(exception);
26+
27+
// then.
28+
Assertions.assertThat(translated).isSameAs(exception);
29+
}
30+
31+
@Test
32+
void testUnrecognizedException() {
33+
34+
// when
35+
DataAccessException translated = new JdbcExceptionTranslator().translateExceptionIfPossible(new IllegalArgumentException());
36+
37+
// then.
38+
Assertions.assertThat(translated).isNull();
39+
}
40+
41+
static Stream<Arguments> passThroughExceptionsSource() {
42+
return Stream.of(Arguments.of(new OptimisticLockingFailureException("")), Arguments.of(new DuplicateKeyException("")));
43+
}
44+
}

spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java

Lines changed: 75 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,12 @@
1515
*/
1616
package org.springframework.data.jdbc.repository;
1717

18-
import static java.util.Arrays.*;
19-
import static java.util.Collections.*;
20-
import static org.assertj.core.api.Assertions.*;
21-
import static org.assertj.core.api.SoftAssertions.*;
18+
import static java.util.Arrays.asList;
19+
import static java.util.Collections.emptyList;
20+
import static java.util.Collections.singletonList;
21+
import static org.assertj.core.api.Assertions.assertThat;
22+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
23+
import static org.assertj.core.api.SoftAssertions.assertSoftly;
2224

2325
import java.io.IOException;
2426
import java.sql.ResultSet;
@@ -37,6 +39,7 @@
3739
import java.util.function.Consumer;
3840
import java.util.stream.Stream;
3941

42+
import org.assertj.core.api.Assertions;
4043
import org.junit.jupiter.api.BeforeEach;
4144
import org.junit.jupiter.api.Test;
4245
import org.junit.jupiter.params.ParameterizedTest;
@@ -49,9 +52,21 @@
4952
import org.springframework.context.annotation.Configuration;
5053
import org.springframework.context.annotation.Import;
5154
import org.springframework.core.io.ClassPathResource;
55+
import org.springframework.dao.DuplicateKeyException;
5256
import org.springframework.dao.IncorrectResultSizeDataAccessException;
5357
import org.springframework.data.annotation.Id;
54-
import org.springframework.data.domain.*;
58+
import org.springframework.data.annotation.Transient;
59+
import org.springframework.data.domain.Example;
60+
import org.springframework.data.domain.ExampleMatcher;
61+
import org.springframework.data.domain.Limit;
62+
import org.springframework.data.domain.Page;
63+
import org.springframework.data.domain.PageRequest;
64+
import org.springframework.data.domain.Pageable;
65+
import org.springframework.data.domain.Persistable;
66+
import org.springframework.data.domain.ScrollPosition;
67+
import org.springframework.data.domain.Slice;
68+
import org.springframework.data.domain.Sort;
69+
import org.springframework.data.domain.Window;
5570
import org.springframework.data.jdbc.core.mapping.AggregateReference;
5671
import org.springframework.data.jdbc.repository.query.Modifying;
5772
import org.springframework.data.jdbc.repository.query.Query;
@@ -64,8 +79,8 @@
6479
import org.springframework.data.jdbc.testing.TestDatabaseFeatures;
6580
import org.springframework.data.relational.core.mapping.Column;
6681
import org.springframework.data.relational.core.mapping.MappedCollection;
67-
import org.springframework.data.relational.core.mapping.Table;
6882
import org.springframework.data.relational.core.mapping.Sequence;
83+
import org.springframework.data.relational.core.mapping.Table;
6984
import org.springframework.data.relational.core.mapping.event.AbstractRelationalEvent;
7085
import org.springframework.data.relational.core.mapping.event.AfterConvertEvent;
7186
import org.springframework.data.relational.core.sql.LockMode;
@@ -104,6 +119,8 @@ public class JdbcRepositoryIntegrationTests {
104119

105120
@Autowired NamedParameterJdbcTemplate template;
106121
@Autowired DummyEntityRepository repository;
122+
123+
@Autowired ProvidedIdEntityRepository providedIdEntityRepository;
107124
@Autowired MyEventListener eventListener;
108125
@Autowired RootRepository rootRepository;
109126
@Autowired WithDelimitedColumnRepository withDelimitedColumnRepository;
@@ -208,6 +225,18 @@ public void findAllFindsAllSpecifiedEntities() {
208225
.containsExactlyInAnyOrder(entity.getIdProp(), other.getIdProp());
209226
}
210227

228+
@Test // DATAJDBC-611
229+
public void testDuplicateKeyExceptionIsThrownInCaseOfUniqueKeyViolation() {
230+
231+
// given.
232+
ProvidedIdEntity first = ProvidedIdEntity.newInstance(1L, "name");
233+
ProvidedIdEntity second = ProvidedIdEntity.newInstance(1L, "other");
234+
235+
// when/then
236+
Assertions.assertThatCode(() -> providedIdEntityRepository.save(first)).doesNotThrowAnyException();
237+
Assertions.assertThatThrownBy(() -> providedIdEntityRepository.save(second)).isInstanceOf(DuplicateKeyException.class);
238+
}
239+
211240
@Test // DATAJDBC-97
212241
public void countsEntities() {
213242

@@ -1436,6 +1465,10 @@ interface DummyProjectExample {
14361465
String getName();
14371466
}
14381467

1468+
interface ProvidedIdEntityRepository extends CrudRepository<ProvidedIdEntity, Long> {
1469+
1470+
}
1471+
14391472
interface DummyEntityRepository extends CrudRepository<DummyEntity, Long>, QueryByExampleExecutor<DummyEntity> {
14401473

14411474
@Lock(LockMode.PESSIMISTIC_WRITE)
@@ -1543,6 +1576,11 @@ DummyEntityRepository dummyEntityRepository() {
15431576
return factory.getRepository(DummyEntityRepository.class);
15441577
}
15451578

1579+
@Bean
1580+
ProvidedIdEntityRepository providedIdEntityRepository() {
1581+
return factory.getRepository(ProvidedIdEntityRepository.class);
1582+
}
1583+
15461584
@Bean
15471585
RootRepository rootRepository() {
15481586
return factory.getRepository(RootRepository.class);
@@ -1886,6 +1924,37 @@ public String getName() {
18861924
}
18871925
}
18881926

1927+
static class ProvidedIdEntity implements Persistable<Long> {
1928+
1929+
@Id
1930+
private final Long id;
1931+
1932+
private String name;
1933+
1934+
@Transient
1935+
private boolean isNew;
1936+
1937+
private ProvidedIdEntity(Long id, String name, boolean isNew) {
1938+
this.id = id;
1939+
this.name = name;
1940+
this.isNew = isNew;
1941+
}
1942+
1943+
private static ProvidedIdEntity newInstance(Long id, String name) {
1944+
return new ProvidedIdEntity(id, name, true);
1945+
}
1946+
1947+
@Override
1948+
public Long getId() {
1949+
return id;
1950+
}
1951+
1952+
@Override
1953+
public boolean isNew() {
1954+
return isNew;
1955+
}
1956+
}
1957+
18891958
static class DummyEntity {
18901959

18911960
String name;

spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-db2.sql

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ DROP TABLE LEAF;
55
DROP TABLE WITH_DELIMITED_COLUMN;
66
DROP TABLE ENTITY_WITH_SEQUENCE;
77
DROP SEQUENCE ENTITY_SEQUENCE;
8+
DROP TABLE PROVIDED_ID_ENTITY;
89

910
CREATE TABLE dummy_entity
1011
(
@@ -55,4 +56,8 @@ CREATE TABLE ENTITY_WITH_SEQUENCE
5556
NAME VARCHAR(100)
5657
);
5758

58-
CREATE SEQUENCE ENTITY_SEQUENCE START WITH 1 INCREMENT BY 1 NO MAXVALUE;
59+
CREATE TABLE PROVIDED_ID_ENTITY
60+
(
61+
ID BIGINT PRIMARY KEY,
62+
NAME VARCHAR(30)
63+
);

spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-h2.sql

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,10 @@ CREATE TABLE ENTITY_WITH_SEQUENCE
4747
NAME VARCHAR(100)
4848
);
4949

50-
CREATE SEQUENCE ENTITY_SEQUENCE START WITH 1 INCREMENT BY 1 NO MAXVALUE;
50+
CREATE SEQUENCE ENTITY_SEQUENCE START WITH 1 INCREMENT BY 1 NO MAXVALUE;
51+
52+
CREATE TABLE PROVIDED_ID_ENTITY
53+
(
54+
ID BIGINT PRIMARY KEY,
55+
NAME VARCHAR(30)
56+
);

spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-hsql.sql

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,10 @@ CREATE TABLE ENTITY_WITH_SEQUENCE
4747
NAME VARCHAR(100)
4848
);
4949

50-
CREATE SEQUENCE ENTITY_SEQUENCE START WITH 1 INCREMENT BY 1 NO MAXVALUE;
50+
CREATE SEQUENCE ENTITY_SEQUENCE START WITH 1 INCREMENT BY 1 NO MAXVALUE;
51+
52+
CREATE TABLE PROVIDED_ID_ENTITY
53+
(
54+
ID BIGINT PRIMARY KEY,
55+
NAME VARCHAR(30)
56+
);

spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-mariadb.sql

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,10 @@ CREATE TABLE ENTITY_WITH_SEQUENCE
4747
NAME VARCHAR(100)
4848
);
4949

50-
CREATE SEQUENCE `ENTITY_SEQUENCE` START WITH 1 INCREMENT BY 1 NO MAXVALUE;
50+
CREATE SEQUENCE `ENTITY_SEQUENCE` START WITH 1 INCREMENT BY 1 NO MAXVALUE;
51+
52+
CREATE TABLE PROVIDED_ID_ENTITY
53+
(
54+
ID BIGINT PRIMARY KEY,
55+
NAME VARCHAR(30)
56+
);

spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-mssql.sql

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ DROP TABLE IF EXISTS LEAF;
55
DROP TABLE IF EXISTS WITH_DELIMITED_COLUMN;
66
DROP TABLE IF EXISTS ENTITY_WITH_SEQUENCE;
77
DROP SEQUENCE IF EXISTS ENTITY_SEQUENCE;
8+
DROP TABLE IF EXISTS PROVIDED_ID_ENTITY;
89

910
CREATE TABLE dummy_entity
1011
(
@@ -55,4 +56,10 @@ CREATE TABLE ENTITY_WITH_SEQUENCE
5556
NAME VARCHAR(100)
5657
);
5758

58-
CREATE SEQUENCE ENTITY_SEQUENCE START WITH 1 INCREMENT BY 1 NO MAXVALUE;
59+
CREATE SEQUENCE ENTITY_SEQUENCE START WITH 1 INCREMENT BY 1 NO MAXVALUE;
60+
61+
CREATE TABLE PROVIDED_ID_ENTITY
62+
(
63+
ID BIGINT PRIMARY KEY,
64+
NAME VARCHAR(30)
65+
);

spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-mysql.sql

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,10 @@ CREATE TABLE WITH_DELIMITED_COLUMN
4242
ID BIGINT AUTO_INCREMENT PRIMARY KEY,
4343
`ORG.XTUNIT.IDENTIFIER` VARCHAR(100),
4444
STYPE VARCHAR(100)
45-
);
45+
);
46+
47+
CREATE TABLE PROVIDED_ID_ENTITY
48+
(
49+
ID BIGINT PRIMARY KEY,
50+
NAME VARCHAR(30)
51+
);

spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-oracle.sql

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ DROP TABLE LEAF CASCADE CONSTRAINTS PURGE;
55
DROP TABLE WITH_DELIMITED_COLUMN CASCADE CONSTRAINTS PURGE;
66
DROP TABLE ENTITY_WITH_SEQUENCE CASCADE CONSTRAINTS PURGE;
77
DROP SEQUENCE ENTITY_SEQUENCE;
8+
DROP TABLE PROVIDED_ID_ENTITY CASCADE CONSTRAINTS PURGE;
89

910
CREATE TABLE DUMMY_ENTITY
1011
(
@@ -55,4 +56,10 @@ CREATE TABLE ENTITY_WITH_SEQUENCE
5556
NAME VARCHAR(100)
5657
);
5758

58-
CREATE SEQUENCE ENTITY_SEQUENCE START WITH 1 INCREMENT BY 1;
59+
CREATE SEQUENCE ENTITY_SEQUENCE START WITH 1 INCREMENT BY 1;
60+
61+
CREATE TABLE PROVIDED_ID_ENTITY
62+
(
63+
ID BIGINT PRIMARY KEY,
64+
NAME VARCHAR(30)
65+
);

0 commit comments

Comments
 (0)