Spring Data JPA提供了Query by Example (QBE) 查詢技術,實現了動態條件查詢,沒必要再寫煩瑣的條件判斷。但QBE不支持範圍查詢,本文結合QBE和Specification實現了動態範圍查詢。java
本文以汪雲飛-Spring Data JPA實現動態條件與範圍查詢實例代碼爲基礎修改,利用org.springframework.data.domain.Range替換了自定義實現,支持Matching Any,保持了原代碼的基本結構。源碼地址 https://github.com/sunjc/heroes-apigit
import org.springframework.data.domain.Range; import org.springframework.data.domain.Range.Bound; import static org.springframework.data.domain.Range.Bound.inclusive; public class FieldRange<T extends Comparable<T>> { private String field; private Range<T> range; public FieldRange(String field, T lower, T upper) { this.field = field; this.range = of(lower, upper); } private Range<T> of(T lower, T upper) { Bound<T> lowerBound = Bound.unbounded(); Bound<T> upperBound = Bound.unbounded(); if (lower != null) { lowerBound = inclusive(lower); } if (upper != null) { upperBound = inclusive(upper); } return Range.of(lowerBound, upperBound); } public String getField() { return field; } public Range<T> getRange() { return range; } }
提取Example的Specification,SimpleJpaRepository內含有此類,是私有的。github
import org.springframework.data.domain.Example; import org.springframework.data.jpa.domain.Specification; import org.springframework.util.Assert; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Root; import static org.springframework.data.jpa.convert.QueryByExamplePredicateBuilder.getPredicate; public class ExampleSpecification<T> implements Specification<T> { private static final long serialVersionUID = 1L; private final Example<T> example; //NOSONAR public ExampleSpecification(Example<T> example) { Assert.notNull(example, "Example must not be null!"); this.example = example; } @Override public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) { return getPredicate(root, criteriaBuilder, example); } }
import org.springframework.data.jpa.domain.Specification; import javax.persistence.criteria.*; import java.util.Optional; public class RangeSpecification<T, E extends Comparable<E>> implements Specification<T> { private FieldRange<E> fieldRange; //NOSONAR public RangeSpecification(FieldRange<E> fieldRange) { this.fieldRange = fieldRange; } @Override public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder builder) { Optional<E> lower = fieldRange.getRange().getLowerBound().getValue(); Optional<E> upper = fieldRange.getRange().getUpperBound().getValue(); Path<E> path = root.get(fieldRange.getField()); if (lower.isPresent() && upper.isPresent()) { return builder.between(path, lower.get(), upper.get()); } if (lower.isPresent()) { return builder.greaterThanOrEqualTo(path, lower.get()); } if (upper.isPresent()) { return builder.lessThanOrEqualTo(path, upper.get()); } return null; } }
WiselyRepository接口spring
import org.itrunner.heroes.repository.specifications.FieldRange; import org.springframework.data.domain.Example; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.repository.NoRepositoryBean; import java.util.List; @NoRepositoryBean public interface WiselyRepository<T, ID> extends JpaRepository<T, ID> { //NOSONAR Page<T> findByExampleAndRange(Example<T> example, List<FieldRange<? extends Comparable>> fieldRanges, Pageable pageable); List<T> findByExampleAndRange(Example<T> example, List<FieldRange<? extends Comparable>> fieldRanges); }
WiselyRepository實現api
import org.itrunner.heroes.repository.WiselyRepository; import org.itrunner.heroes.repository.specifications.ExampleSpecification; import org.itrunner.heroes.repository.specifications.FieldRange; import org.itrunner.heroes.repository.specifications.RangeSpecification; import org.springframework.data.domain.Example; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.domain.Specification; import org.springframework.data.jpa.repository.support.JpaEntityInformation; import org.springframework.data.jpa.repository.support.SimpleJpaRepository; import javax.persistence.EntityManager; import java.util.ArrayList; import java.util.List; import static org.springframework.data.jpa.domain.Specification.where; public class WiselyRepositoryImpl<T, ID> extends SimpleJpaRepository<T, ID> implements WiselyRepository<T, ID> { //NOSONAR public WiselyRepositoryImpl(JpaEntityInformation<T, ?> entityInformation, EntityManager entityManager) { super(entityInformation, entityManager); } @Override public Page<T> findByExampleAndRange(Example<T> example, List<FieldRange<? extends Comparable>> fieldRanges, Pageable pageable) { return findAll(specifications(example, fieldRanges), pageable); } @Override public List<T> findByExampleAndRange(Example<T> example, List<FieldRange<? extends Comparable>> fieldRanges) { return findAll(specifications(example, fieldRanges)); } private Specification<T> specifications(Example<T> example, List<FieldRange<? extends Comparable>> fieldRanges) { boolean allMatching = example.getMatcher().isAllMatching(); Specification<T> byExample = new ExampleSpecification<>(example); List<Specification<T>> byRanges = getRangeSpecifications(fieldRanges); return conjunction(byExample, byRanges, allMatching); } private List<Specification<T>> getRangeSpecifications(List<FieldRange<? extends Comparable>> fieldRanges) { List<Specification<T>> rangeSpecifications = new ArrayList<>(); for (FieldRange fieldRange : fieldRanges) { rangeSpecifications.add(new RangeSpecification<>(fieldRange)); } return rangeSpecifications; } private Specification<T> conjunction(Specification<T> byExample, List<Specification<T>> byRanges, boolean allMatching) { Specification<T> specification = where(byExample); for (Specification<T> rangeSpecification : byRanges) { if (allMatching) { specification = specification.and(rangeSpecification); } else { specification = specification.or(rangeSpecification); } } return specification; } }
啓用WiselyRepositoryImpl:less
@EnableJpaRepositories(repositoryBaseClass = WiselyRepositoryImpl.class)
調用範圍查詢:dom
public Page<Hero> findHeroes(Hero hero, Date startDate, Date endDate, int minAge, int maxAge, Pageable pageable) { List<FieldRange<? extends Comparable>> fieldRanges = new ArrayList<>(); fieldRanges.add(new FieldRange<>("birthday", startDate, endDate)); fieldRanges.add(new FieldRange<>("age", minAge, maxAge)); return heroRepository.findByExampleAndRange(of(hero), fieldRanges, pageable); }