Skip to content

Commit 2d76885

Browse files
schaudermp911de
authored andcommitted
Correct behavior of NOOP deletes to match the specification in CrudRepository.
Delete operations that receive a version attribute throw an `OptimisticFailureException` when they delete zero rows. Otherwise, the NOOP delete gets silently ignored. Note that save operations that are determined to be an update because the aggregate is not new will still throw an `IncorrectUpdateSemanticsDataAccessException` if they fail to update any row. This is somewhat asymmetric to the delete-behaviour. But with a delete the intended result is achieved: the aggregate is gone from the database. For save operations the intended result is not achieved, hence the exception. Closes #1313 Original pull request: #1314. See spring-projects/spring-data-commons#2651
1 parent d95b559 commit 2d76885

File tree

3 files changed

+22
-6
lines changed

3 files changed

+22
-6
lines changed

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

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

18+
import org.springframework.dao.OptimisticLockingFailureException;
1819
import org.springframework.data.jdbc.core.convert.DataAccessStrategy;
1920
import org.springframework.data.jdbc.core.convert.JdbcConverter;
2021
import org.springframework.data.relational.core.conversion.AggregateChange;
@@ -87,6 +88,10 @@ private void execute(DbAction<?> action, JdbcAggregateChangeExecutionContext exe
8788
throw new RuntimeException("unexpected action");
8889
}
8990
} catch (Exception e) {
91+
92+
if (e instanceof OptimisticLockingFailureException) {
93+
throw e;
94+
}
9095
throw new DbActionExecutionException(action, e);
9196
}
9297
}

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

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

18+
import org.springframework.dao.IncorrectUpdateSemanticsDataAccessException;
1819
import org.springframework.data.domain.Page;
1920
import org.springframework.data.domain.Pageable;
2021
import org.springframework.data.domain.Sort;
@@ -35,6 +36,8 @@ public interface JdbcAggregateOperations {
3536
* @param instance the aggregate root of the aggregate to be saved. Must not be {@code null}.
3637
* @param <T> the type of the aggregate root.
3738
* @return the saved instance.
39+
* @throws IncorrectUpdateSemanticsDataAccessException when the instance is determined to be not new and the resulting
40+
* update does not update any rows.
3841
*/
3942
<T> T save(T instance);
4043

@@ -62,6 +65,11 @@ public interface JdbcAggregateOperations {
6265

6366
/**
6467
* Deletes a single Aggregate including all entities contained in that aggregate.
68+
* <p>
69+
* Since no version attribute is provided this method will never throw a
70+
* {@link org.springframework.dao.OptimisticLockingFailureException}. If no rows match the generated delete operation
71+
* this fact will be silently ignored.
72+
* </p>
6573
*
6674
* @param id the id of the aggregate root of the aggregate to be deleted. Must not be {@code null}.
6775
* @param domainType the type of the aggregate root.
@@ -75,6 +83,9 @@ public interface JdbcAggregateOperations {
7583
* @param aggregateRoot to delete. Must not be {@code null}.
7684
* @param domainType the type of the aggregate root. Must not be {@code null}.
7785
* @param <T> the type of the aggregate root.
86+
* @throws org.springframework.dao.OptimisticLockingFailureException when {@literal T} has a version attribute and the
87+
* version attribute of the provided entity does not match the version attribute in the database, or when
88+
* there is no aggregate root with matching id. In other cases a NOOP delete is silently ignored.
7889
*/
7990
<T> void delete(T aggregateRoot, Class<T> domainType);
8091

spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateTemplateIntegrationTests.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -828,11 +828,11 @@ void saveAndUpdateAggregateWithImmutableVersion() {
828828

829829
assertThatThrownBy(() -> template.save(new AggregateWithImmutableVersion(id, 0L)))
830830
.describedAs("saving an aggregate with an outdated version should raise an exception")
831-
.hasRootCauseInstanceOf(OptimisticLockingFailureException.class);
831+
.isInstanceOf(OptimisticLockingFailureException.class);
832832

833833
assertThatThrownBy(() -> template.save(new AggregateWithImmutableVersion(id, 2L)))
834834
.describedAs("saving an aggregate with a future version should raise an exception")
835-
.hasRootCauseInstanceOf(OptimisticLockingFailureException.class);
835+
.isInstanceOf(OptimisticLockingFailureException.class);
836836
}
837837

838838
@Test // GH-1137
@@ -877,12 +877,12 @@ void deleteAggregateWithVersion() {
877877
assertThatThrownBy(
878878
() -> template.delete(new AggregateWithImmutableVersion(id, 0L), AggregateWithImmutableVersion.class))
879879
.describedAs("deleting an aggregate with an outdated version should raise an exception")
880-
.hasRootCauseInstanceOf(OptimisticLockingFailureException.class);
880+
.isInstanceOf(OptimisticLockingFailureException.class);
881881

882882
assertThatThrownBy(
883883
() -> template.delete(new AggregateWithImmutableVersion(id, 2L), AggregateWithImmutableVersion.class))
884884
.describedAs("deleting an aggregate with a future version should raise an exception")
885-
.hasRootCauseInstanceOf(OptimisticLockingFailureException.class);
885+
.isInstanceOf(OptimisticLockingFailureException.class);
886886

887887
// This should succeed
888888
template.delete(aggregate, AggregateWithImmutableVersion.class);
@@ -1047,12 +1047,12 @@ private <T extends Number> void saveAndUpdateAggregateWithVersion(VersionedAggre
10471047
reloadedAggregate.setVersion(toConcreteNumber.apply(initialId));
10481048
assertThatThrownBy(() -> template.save(reloadedAggregate))
10491049
.withFailMessage("saving an aggregate with an outdated version should raise an exception")
1050-
.hasRootCauseInstanceOf(OptimisticLockingFailureException.class);
1050+
.isInstanceOf(OptimisticLockingFailureException.class);
10511051

10521052
reloadedAggregate.setVersion(toConcreteNumber.apply(initialId + 2));
10531053
assertThatThrownBy(() -> template.save(reloadedAggregate))
10541054
.withFailMessage("saving an aggregate with a future version should raise an exception")
1055-
.hasRootCauseInstanceOf(OptimisticLockingFailureException.class);
1055+
.isInstanceOf(OptimisticLockingFailureException.class);
10561056
}
10571057

10581058
private Long count(String tableName) {

0 commit comments

Comments
 (0)