Skip to content

Commit c0cadfa

Browse files
committed
Introduce delete(Specification) on JpaSpecificationExecutor.
Ever since JPA 2.1, CriteriaBuilder has offered createCriteriaDelete, returning a CriteriaDelete. With Spring Data JPA 3.0 rebased on JPA 3.0, we are able to guarantee this SPI, and hence rollout support for this feature. See #1262.
1 parent 9049594 commit c0cadfa

File tree

5 files changed

+53
-0
lines changed

5 files changed

+53
-0
lines changed

spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/Specification.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package org.springframework.data.jpa.domain;
1717

1818
import jakarta.persistence.criteria.CriteriaBuilder;
19+
import jakarta.persistence.criteria.CriteriaDelete;
1920
import jakarta.persistence.criteria.CriteriaQuery;
2021
import jakarta.persistence.criteria.Predicate;
2122
import jakarta.persistence.criteria.Root;

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/JpaSpecificationExecutor.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,14 @@ public interface JpaSpecificationExecutor<T> {
8787
*/
8888
boolean exists(Specification<T> spec);
8989

90+
/**
91+
* Deletes by the {@link Specification} and returns the number of rows deleted.
92+
*
93+
* @param spec the {@link Specification} to use for the existence check. Must not be {@literal null}.
94+
* @return the number of entities deleted
95+
*/
96+
long delete(Specification<T> spec);
97+
9098
/**
9199
* Returns entities matching the given {@link Specification} applying the {@code queryFunction} that defines the query
92100
* and its result type.

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import jakarta.persistence.Query;
2525
import jakarta.persistence.TypedQuery;
2626
import jakarta.persistence.criteria.CriteriaBuilder;
27+
import jakarta.persistence.criteria.CriteriaDelete;
2728
import jakarta.persistence.criteria.CriteriaQuery;
2829
import jakarta.persistence.criteria.ParameterExpression;
2930
import jakarta.persistence.criteria.Path;
@@ -485,6 +486,21 @@ public boolean exists(Specification<T> spec) {
485486
return query.setMaxResults(1).getResultList().size() == 1;
486487
}
487488

489+
@Override
490+
public long delete(Specification<T> spec) {
491+
492+
CriteriaBuilder builder = this.em.getCriteriaBuilder();
493+
CriteriaDelete<T> delete = builder.createCriteriaDelete(getDomainClass());
494+
495+
Predicate predicate = spec.toPredicate(delete.from(getDomainClass()), null, builder);
496+
497+
if (predicate != null) {
498+
delete.where(predicate);
499+
}
500+
501+
return this.em.createQuery(delete).executeUpdate();
502+
}
503+
488504
@Override
489505
public <S extends T> List<S> findAll(Example<S> example) {
490506
return getQuery(new ExampleSpecification<>(example, escapeCharacter), example.getProbeType(), Sort.unsorted())

spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2838,6 +2838,19 @@ void existsWithSpec() {
28382838
assertThat(repository.exists(hundredYearsOld)).isTrue();
28392839
}
28402840

2841+
@Test // GH-1262
2842+
void deleteWithSpec() {
2843+
2844+
flushTestUsers();
2845+
2846+
Specification<User> usersWithEInTheirName = userHasFirstnameLike("e");
2847+
2848+
long initialCount = repository.count();
2849+
assertThat(repository.delete(usersWithEInTheirName)).isEqualTo(3L);
2850+
long finalCount = repository.count();
2851+
assertThat(initialCount - finalCount).isEqualTo(3L);
2852+
}
2853+
28412854
private Page<User> executeSpecWithSort(Sort sort) {
28422855

28432856
flushTestUsers();

src/main/asciidoc/jpa.adoc

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -943,6 +943,21 @@ List<Customer> customers = customerRepository.findAll(
943943
`Specification` offers some "`glue-code`" default methods to chain and combine `Specification` instances. These methods let you extend your data access layer by creating new `Specification` implementations and combining them with already existing implementations.
944944
====
945945

946+
And with JPA 2.1, the `CriteriaBuilder` API introduced `CriteriaDelete`. This is provided through `JpaSpecificationExecutor`'s `delete(Specification)` API.
947+
948+
.Using a `Specification` to delete entries.
949+
====
950+
[source, java]
951+
----
952+
Specification<User> ageLessThan18 = (root, query, cb) -> cb.lessThan(root.get("age").as(Integer.class), 18)
953+
954+
userRepository.delete(ageLessThan18);
955+
----
956+
The `Specification` builds up a criteria where the `age` field (cast as an integer) is less than `18`.
957+
Passed on to the `userRepository`, it will use JPA's `CriteriaDelete` feature to generate the right `DELETE` operation.
958+
It then returns the number of entities deleted.
959+
====
960+
946961
include::{spring-data-commons-docs}/query-by-example.adoc[leveloffset=+1]
947962
include::query-by-example.adoc[leveloffset=+1]
948963

0 commit comments

Comments
 (0)