-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Description
JpaSpecificationExecutor
provides some methods that accept Specification
as a parameter to perform query condition filtering. Specification
uses CriteriaBuilder
to build filter expression. Developer can use List.add
to temporarily store dynamic Predicate
conditions, and finally use CriteriaBuilder
.and to construct the final Predicate
.
default Specification<SysDepartPo> buildSpecification1(SysDepartQueryCondition condition) {
return (root, cq, cb) -> {
var predicateList = new ArrayList<Predicate>();
if (StringUtils.hasText(condition.getName())) {
predicateList.add(cb.like(root.get("name"), "%" + condition.getName() + "%"));
}
if (condition.getOrganizationId() != null) {
predicateList.add(cb.equal(root.get("organizationId"), condition.getOrganizationId()));
}
return cb.and(predicateList.toArray(new Predicate[0]));
};
}
In addition, I can combine Specification
default Specification<SysDepartPo> buildSpecification2(SysDepartQueryCondition condition) {
Specification<SysDepartPo> spec = (root, cq, cb) -> null;
if (StringUtils.hasText(condition.getName())) {
spec = spec.and((root, cq, cb) -> cb.like(root.get("name"), "%" + condition.getName() + "%"));
}
if (condition.getOrganizationId() != null) {
spec = spec.and((root, cq, cb) -> cb.equal(root.get("organizationId"), condition.getOrganizationId()));
}
return spec;
}
This got me thinking about whether I could make the whole search a little more convenient without having to deal with combinations. Here’s what I’m currently using:
default Specification<SysDepartPo> buildSpecification3(SysDepartQueryCondition condition) {
Specifications<SysDepartPo> spec = Specifications.builder(SysDepartPo.class);
spec.likeAll(StringUtils.hasText(condition.getName()), SysDepartPo::getName, condition.getName());
spec.eq(SysDepartPo::getOrganizationId, condition.getOrganizationId());
return spec;
}
By chaining, the constant combination and assignment of Specification
is avoided.
The condition is embedded in the method to avoid if.
In addition, I also used SerializedLambda
to obtain attributes. I know it is not a mainstream method in the jpa community. I should support SingularAttribute
, but I don’t want to generate jpamodel. Although it has stronger reference capabilities, it is not more convenient than static references in handling basic types.
The following is my implementation of Specifications
, which is not perfect. The supported parameter types are not complete, and the coverage of the Criteria API
is not sufficient, but I hope that the community will support this kind of chain mode in the future.
package io.naccoll.boilerplate.core.persistence.jpa.specification;
import io.naccoll.boilerplate.core.interfaces.function.PropertyFunc;
import jakarta.persistence.criteria.*;
import org.springframework.data.jpa.domain.Specification;
import java.util.Collection;
import java.util.function.Consumer;
import static io.naccoll.boilerplate.core.utils.PojoHelper.getPropertyName;
/**
* The type Specification builder.
*
* @param <T> the type parameter
* @author NaccOll
*/
public class Specifications<T> implements Specification<T> {
private Specification<T> specification;
private boolean notCondition;
private boolean orCondition;
private Specifications() {
}
/**
* Builder specification builder.
* @param <T> the type parameter
* @return the specification builder
*/
public static <T> Specifications<T> builder() {
return new Specifications<>();
}
public static <T> Specifications<T> builder(Class<T> tClass) {
return new Specifications<>();
}
public <R> Specifications<T> like(PropertyFunc<T, R> attribute, String value) {
return like(true, attribute, value);
}
public <R> Specifications<T> like(boolean condition, PropertyFunc<T, R> attribute, String value) {
return maybeDo(condition, () -> (root, query, cb) -> cb.like(root.get(getPropertyName(attribute)), value));
}
public <R> Specifications<T> endWith(PropertyFunc<T, R> attribute, String value) {
return endWith(true, attribute, value);
}
public <R> Specifications<T> endWith(boolean condition, PropertyFunc<T, R> attribute, String value) {
return maybeDo(condition,
() -> (root, query, cb) -> cb.like(root.get(getPropertyName(attribute)), "%" + value));
}
public <R> Specifications<T> startWith(PropertyFunc<T, R> attribute, String value) {
return startWith(true, attribute, value);
}
public <R> Specifications<T> startWith(boolean condition, PropertyFunc<T, R> attribute, String value) {
return maybeDo(condition,
() -> (root, query, cb) -> cb.like(root.get(getPropertyName(attribute)), value + "%"));
}
public <R> Specifications<T> contain(PropertyFunc<T, R> attribute, String value) {
return contain(true, attribute, value);
}
public <R> Specifications<T> contain(boolean condition, PropertyFunc<T, R> attribute, String value) {
return maybeDo(condition,
() -> (root, query, cb) -> cb.like(root.get(getPropertyName(attribute)), "%" + value + "%"));
}
public <R> Specifications<T> isNull(PropertyFunc<T, R> attribute) {
return isNull(true, attribute);
}
public <R> Specifications<T> isNull(boolean condition, PropertyFunc<T, R> attribute) {
return maybeDo(condition,
() -> (root, query, criteriaBuilder) -> root.get(getPropertyName(attribute)).isNull());
}
public <R> Specifications<T> isNotNull(PropertyFunc<T, R> attribute) {
return isNotNull(true, attribute);
}
public <R> Specifications<T> isNotNull(boolean condition, PropertyFunc<T, R> attribute) {
return maybeDo(condition,
() -> (root, query, criteriaBuilder) -> root.get(getPropertyName(attribute)).isNotNull());
}
public <R> Specifications<T> eq(PropertyFunc<T, R> attribute, R value) {
return eq(true, attribute, value);
}
public <R> Specifications<T> eq(boolean condition, PropertyFunc<T, R> attribute, R value) {
return maybeDo(condition, () -> (root, query, cb) -> cb.equal(root.get(getPropertyName(attribute)), value));
}
public <R> Specifications<T> ne(PropertyFunc<T, R> attribute, R value) {
return ne(true, attribute, value);
}
public <R> Specifications<T> ne(boolean condition, PropertyFunc<T, R> attribute, R value) {
return maybeDo(condition, () -> (root, query, cb) -> cb.notEqual(root.get(getPropertyName(attribute)), value));
}
public <R> Specifications<T> in(PropertyFunc<T, R> attribute, Collection<R> collection) {
return in(true, attribute, collection);
}
public <R> Specifications<T> in(boolean condition, PropertyFunc<T, R> attribute, Collection<R> collection) {
return maybeDo(condition, () -> (root, query, cb) -> root.get(getPropertyName(attribute)).in(collection));
}
public <R> Specifications<T> notIn(PropertyFunc<T, R> attribute, Collection<R> collection) {
return notIn(true, attribute, collection);
}
public <R> Specifications<T> notIn(boolean condition, PropertyFunc<T, R> attribute, Collection<R> collection) {
return maybeDo(condition, () -> (root, query, criteriaBuilder) -> criteriaBuilder
.not(root.get(getPropertyName(attribute)).in(collection)));
}
public <R> Specifications<T> member(PropertyFunc<T, R> attribute, R collection) {
return member(true, attribute, collection);
}
public <R> Specifications<T> member(boolean condition, PropertyFunc<T, R> attribute, R collection) {
return maybeDo(condition, () -> (root, query, criteriaBuilder) -> criteriaBuilder.isMember(collection,
root.get(getPropertyName(attribute))));
}
public <R> Specifications<T> notMember(PropertyFunc<T, R> attribute, R collection) {
return notMember(true, attribute, collection);
}
public <R> Specifications<T> notMember(boolean condition, PropertyFunc<T, R> attribute, R collection) {
return maybeDo(condition, () -> (root, query, criteriaBuilder) -> criteriaBuilder.isNotMember(collection,
root.get(getPropertyName(attribute))));
}
public <R extends Comparable<R>> Specifications<T> gt(PropertyFunc<T, R> attribute, R value) {
return gt(true, attribute, value);
}
public <R extends Comparable<R>> Specifications<T> gt(boolean condition, PropertyFunc<T, R> attribute, R value) {
return maybeDo(condition,
() -> (root, query, cb) -> cb.greaterThan(root.get(getPropertyName(attribute)), value));
}
public <R extends Comparable<R>> Specifications<T> ge(PropertyFunc<T, R> attribute, R value) {
return ge(true, attribute, value);
}
public <R extends Comparable<R>> Specifications<T> ge(boolean condition, PropertyFunc<T, R> attribute, R value) {
return maybeDo(condition,
() -> (root, query, cb) -> cb.greaterThanOrEqualTo(root.get(getPropertyName(attribute)), value));
}
public <R extends Comparable<R>> Specifications<T> lt(PropertyFunc<T, R> attribute, R value) {
return lt(true, attribute, value);
}
public <R extends Comparable<R>> Specifications<T> lt(boolean condition, PropertyFunc<T, R> attribute, R value) {
return maybeDo(condition, () -> (root, query, cb) -> cb.lessThan(root.get(getPropertyName(attribute)), value));
}
public <R extends Comparable<R>> Specifications<T> le(PropertyFunc<T, R> attribute, R value) {
return le(true, attribute, value);
}
public <R extends Comparable<R>> Specifications<T> le(boolean condition, PropertyFunc<T, R> attribute, R value) {
return maybeDo(condition,
() -> (root, query, cb) -> cb.lessThanOrEqualTo(root.get(getPropertyName(attribute)), value));
}
public <R extends Comparable<R>> Specifications<T> between(PropertyFunc<T, R> attribute, R min, R max) {
return between(true, attribute, min, max);
}
public <R extends Comparable<R>> Specifications<T> between(boolean condition, PropertyFunc<T, R> attribute, R min,
R max) {
return maybeDo(condition,
() -> (root, query, cb) -> cb.between(root.get(getPropertyName(attribute)), min, max));
}
public <R> Specifications<T> isTrue(PropertyFunc<T, R> attribute) {
return isTrue(true, attribute);
}
public <R> Specifications<T> isTrue(boolean condition, PropertyFunc<T, R> attribute) {
return maybeDo(condition, () -> (root, query, cb) -> cb.isTrue(root.get(getPropertyName(attribute))));
}
public <R> Specifications<T> isFalse(PropertyFunc<T, R> attribute) {
return isFalse(true, attribute);
}
public <R> Specifications<T> isFalse(boolean condition, PropertyFunc<T, R> attribute) {
return maybeDo(condition, () -> (root, query, cb) -> cb.isFalse(root.get(getPropertyName(attribute))));
}
@Override
public Specifications<T> and(Specification<T> andSpecification) {
return and(true, andSpecification);
}
public Specifications<T> and(boolean condition, Specification<T> andSpecification) {
return maybeDo(condition, () -> {
notCondition = false;
orCondition = false;
return andSpecification;
});
}
public Specifications<T> and(Consumer<Specifications<T>> consumer) {
return and(true, consumer);
}
public Specifications<T> and(boolean condition, Consumer<Specifications<T>> consumer) {
Specifications<T> andBuilder = new Specifications<>();
consumer.accept(andBuilder);
maybeDo(condition, () -> {
notCondition = false;
orCondition = false;
return andBuilder;
});
return this;
}
public Specifications<T> not() {
notCondition = true;
return this;
}
public Specifications<T> not(Specification<T> notSpecification) {
return not(true, notSpecification);
}
public Specifications<T> not(boolean condition, Specification<T> notSpecification) {
maybeDo(condition, () -> {
notCondition = true;
return notSpecification;
});
return this;
}
public Specifications<T> not(Consumer<Specifications<T>> consumer) {
return or(true, consumer);
}
public Specifications<T> not(boolean condition, Consumer<Specifications<T>> consumer) {
Specifications<T> notBuilder = new Specifications<>();
consumer.accept(notBuilder);
maybeDo(condition, () -> {
notCondition = true;
return notBuilder;
});
return this;
}
public Specifications<T> or() {
orCondition = true;
return this;
}
@Override
public Specifications<T> or(Specification<T> orSpecification) {
return or(true, orSpecification);
}
public Specifications<T> or(boolean condition, Specification<T> orSpecification) {
maybeDo(condition, () -> {
orCondition = true;
return orSpecification;
});
return this;
}
public Specifications<T> or(Consumer<Specifications<T>> consumer) {
return or(true, consumer);
}
public Specifications<T> or(boolean condition, Consumer<Specifications<T>> consumer) {
Specifications<T> orBuilder = new Specifications<>();
consumer.accept(orBuilder);
maybeDo(condition, () -> {
orCondition = true;
return orBuilder;
});
return this;
}
protected Specifications<T> maybeDo(boolean condition, DoSomething<T> something) {
if (condition) {
Specification<T> newSpecification = something.doIt();
if (notCondition) {
newSpecification = Specification.not(newSpecification);
notCondition = false;
}
if (specification != null) {
if (orCondition) {
specification = specification.or(newSpecification);
orCondition = false;
}
else {
specification = specification.and(newSpecification);
}
}
else {
specification = newSpecification;
}
}
return this;
}
@Override
public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
if (specification == null) {
specification = (r, cq, cb) -> null;
}
return specification.toPredicate(root, query, criteriaBuilder);
}
@FunctionalInterface
public interface DoSomething<T> {
/**
* 执行获取Specification
* @return
*/
Specification<T> doIt();
}
}