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.
This commit is contained in:
Greg L. Turnquist
2022-05-05 16:20:51 -05:00
parent 9049594908
commit c0cadfa400
5 changed files with 53 additions and 0 deletions

View File

@@ -16,6 +16,7 @@
package org.springframework.data.jpa.domain;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaDelete;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Predicate;
import jakarta.persistence.criteria.Root;

View File

@@ -87,6 +87,14 @@ public interface JpaSpecificationExecutor<T> {
*/
boolean exists(Specification<T> spec);
/**
* Deletes by the {@link Specification} and returns the number of rows deleted.
*
* @param spec the {@link Specification} to use for the existence check. Must not be {@literal null}.
* @return the number of entities deleted
*/
long delete(Specification<T> spec);
/**
* Returns entities matching the given {@link Specification} applying the {@code queryFunction} that defines the query
* and its result type.

View File

@@ -24,6 +24,7 @@ import jakarta.persistence.Parameter;
import jakarta.persistence.Query;
import jakarta.persistence.TypedQuery;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaDelete;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.ParameterExpression;
import jakarta.persistence.criteria.Path;
@@ -485,6 +486,21 @@ public class SimpleJpaRepository<T, ID> implements JpaRepositoryImplementation<T
return query.setMaxResults(1).getResultList().size() == 1;
}
@Override
public long delete(Specification<T> spec) {
CriteriaBuilder builder = this.em.getCriteriaBuilder();
CriteriaDelete<T> delete = builder.createCriteriaDelete(getDomainClass());
Predicate predicate = spec.toPredicate(delete.from(getDomainClass()), null, builder);
if (predicate != null) {
delete.where(predicate);
}
return this.em.createQuery(delete).executeUpdate();
}
@Override
public <S extends T> List<S> findAll(Example<S> example) {
return getQuery(new ExampleSpecification<>(example, escapeCharacter), example.getProbeType(), Sort.unsorted())

View File

@@ -2838,6 +2838,19 @@ public class UserRepositoryTests {
assertThat(repository.exists(hundredYearsOld)).isTrue();
}
@Test // GH-1262
void deleteWithSpec() {
flushTestUsers();
Specification<User> usersWithEInTheirName = userHasFirstnameLike("e");
long initialCount = repository.count();
assertThat(repository.delete(usersWithEInTheirName)).isEqualTo(3L);
long finalCount = repository.count();
assertThat(initialCount - finalCount).isEqualTo(3L);
}
private Page<User> executeSpecWithSort(Sort sort) {
flushTestUsers();

View File

@@ -943,6 +943,21 @@ List<Customer> customers = customerRepository.findAll(
`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.
====
And with JPA 2.1, the `CriteriaBuilder` API introduced `CriteriaDelete`. This is provided through `JpaSpecificationExecutor`'s `delete(Specification)` API.
.Using a `Specification` to delete entries.
====
[source, java]
----
Specification<User> ageLessThan18 = (root, query, cb) -> cb.lessThan(root.get("age").as(Integer.class), 18)
userRepository.delete(ageLessThan18);
----
The `Specification` builds up a criteria where the `age` field (cast as an integer) is less than `18`.
Passed on to the `userRepository`, it will use JPA's `CriteriaDelete` feature to generate the right `DELETE` operation.
It then returns the number of entities deleted.
====
include::{spring-data-commons-docs}/query-by-example.adoc[leveloffset=+1]
include::query-by-example.adoc[leveloffset=+1]