Spring Data JPA implements dynamic condition and range query

Spring Data JPAIt is provided for us Query With Exampleto realize dynamic conditional query. When the query condition is empty, we do not need to do a lot of conditional judgment. But it Query With Exampledoes not support range query (including date range, value range query). This paper Specificationimplements a method that supports both dynamic conditional query and range query.

 

Well-typed address: Spring Data JPA implements dynamic conditions and range queries

1 Implementation method

1.1 Range object Range definition

import java.io.Serializable;


public class Range<E> implements Serializable {
    private static final long serialVersionUID = 1L;

    private String field;
    private Comparable from;
    private Comparable to;
    private Boolean includeNull;


    public Range(String field) {
        this.field = field;
    }


    public Range(String field, Comparable from, Comparable to) {
        this.field = field;
        this.from = from;
        this.to = to;
    }

    public Range(String field, Comparable from, Comparable to, Boolean includeNull) {
        this.field = field;
        this.from = from;
        this.to = to;
        this.includeNull = includeNull;
    }


    public Range(Range<E> other) {
        this.field = other.getField();
        this.from = other.getFrom();
        this.to = other.getTo();
        this.includeNull = other.getIncludeNull();
    }

    public String getField() {
        return field;
    }

    public Comparable getFrom() {
        return from;
    }


    public void setFrom(Comparable from) {
        this.from = from;
    }

    public boolean isFromSet() {
        return getFrom() != null;
    }


    public Comparable getTo() {
        return to;
    }

    public void setTo(Comparable to) {
        this.to = to;
    }

    public boolean isToSet() {
        return getTo() != null;
    }

    public void setIncludeNull(boolean includeNull) {
        this.includeNull = includeNull;
    }

    public Boolean getIncludeNull() {
        return includeNull;
    }

    public boolean isIncludeNullSet() {
        return includeNull != null;
    }

    public boolean isBetween() {
        return isFromSet() && isToSet();
    }

    public boolean isSet() {
        return isFromSet() || isToSet() || isIncludeNullSet();
    }

    public boolean isValid() {
        if (isBetween()) {
            return getFrom().compareTo(getTo()) <= 0;
        }

        return true;
    }
}

1.2 example的Specification

import org.springframework.data.domain.Example;
import org.springframework.data.jpa.convert.QueryByExamplePredicateBuilder;
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;

/**
 * Created by wangyunfei on 2017/6/6.
 */
public class ByExampleSpecification<T> implements Specification<T> {
    private final Example<T> example;

    public ByExampleSpecification(Example<T> example) {

        Assert.notNull(example, "Example must not be null!");
        this.example = example;
    }


    @Override
    public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
        return QueryByExamplePredicateBuilder.getPredicate(root, cb, example);
    }
}

1.3 Range的Specification

import org.springframework.data.jpa.domain.Specification;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.util.List;

import static com.google.common.collect.Iterables.toArray;
import static com.google.common.collect.Lists.newArrayList;
import static java.lang.Boolean.FALSE;
import static java.lang.Boolean.TRUE;

/**
 * Created by wangyunfei on 2017/6/6.
 */
public class  ByRangeSpecification<T> implements Specification<T> {
    private  final  List<Range<T>> ranges;

    public  ByRangeSpecification(List<Range<T>> ranges) {
        this.ranges = ranges;
    }

    @Override
    public  Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
        List<Predicate> predicates = newArrayList();

        for (Range<T> range : ranges) {
            if (range.isSet()) {
                Predicate rangePredicate = buildRangePredicate(range, root, builder);

                if (rangePredicate != null) {
                    if (!range.isIncludeNullSet() || range.getIncludeNull() == FALSE) {
                        predicates.add(rangePredicate);
                    } else {
                        predicates.add(builder.or(rangePredicate, builder.isNull(root.get(range.getField()))));
                    }
                }

                if (TRUE == range.getIncludeNull()) {
                    predicates.add(builder.isNull(root.get(range.getField())));
                } else if (FALSE == range.getIncludeNull()) {
                    predicates.add(builder.isNotNull(root.get(range.getField())));
                }
            }
        }
        return predicates.isEmpty() ? builder.conjunction() : builder.and(toArray(predicates, Predicate.class));
    }

    private Predicate buildRangePredicate(Range<T> range, Root<T> root, CriteriaBuilder builder) {
        if (range.isBetween()) {
            return builder.between(root.get(range.getField()), range.getFrom(), range.getTo());
        } else if (range.isFromSet()) {
            return builder.greaterThanOrEqualTo(root.get(range.getField()), range.getFrom());
        } else if (range.isToSet()) {
            return builder.lessThanOrEqualTo(root.get(range.getField()), range.getTo());
        }
        return null;
    }

}

1.4 Custom Repository and Implementation

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.io.Serializable;
import java.util.List;

@NoRepositoryBean
public interface WiselyRepository<E, PK extends Serializable> extends JpaRepository<E, PK> {


 Page<E> queryByExampleWithRange(Example example,List<Range<E>> ranges, Pageable pageable);

}
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.io.Serializable;
import java.util.List;

import static org.springframework.data.jpa.domain.Specifications.where;


public class WiselyRepositoryImpl<E, PK extends Serializable> extends SimpleJpaRepository<E, PK> implements
        WiselyRepository<E, PK> {
    private final EntityManager entityManager;

    public WiselyRepositoryImpl(JpaEntityInformation entityInformation, EntityManager entityManager) {
        super(entityInformation, entityManager);
        this.entityManager = entityManager;
    }



    @Override
    public Page<E> queryByExampleWithRange(Example example, List<Range<E>> ranges, Pageable pageable) {
        Specification<E> byExample = new ByExampleSpecification<>(example);
        Specification<E> byRanges = new ByRangeSpecification<>(ranges);
        return findAll(where(byExample).and(byRanges),pageable);
    }
}

2 How to use

2.1 Enable support

By @EnableJpaRepositories(repositoryBaseClass = WiselyRepositoryImpl.class)turning on support for defined functions.

2.2 Example

  • entity class
@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person {
    @Id
    @GeneratedValue
    private Long id;
    private String name;
    private Integer height;
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date birthday;
}

  • PersonRepository
public interface PersonRepository extends WiselyRepository<Person,Long> {
}
  • test controller
@RestController
@RequestMapping("/people")
public class PersonController {

    @Autowired
    PersonRepository personRepository;

    @PostMapping
    public ResponseEntity<Person> save(@RequestBody Person person){
        Person p = personRepository.save(person);
        return new ResponseEntity<Person>(p, HttpStatus.CREATED);
    }


    @GetMapping
    public ResponseEntity<Page<Person>> query(Person person,
                                              @DateTimeFormat(pattern = "yyyy-MM-dd")Date startDate,
                                              @DateTimeFormat(pattern = "yyyy-MM-dd")Date endDate,
                                              Integer startHeight,
                                              Integer endHeight,
                                              Pageable pageable){
        Example<Person> personExample = Example.of(person);

        List<Range<Person>> ranges = newArrayList();
        Range<Person> birthRange = new Range<Person>("birthday",startDate,endDate);
        Range<Person> heightRange = new Range<Person>("height",startHeight,endHeight);
        ranges.add(birthRange);
        ranges.add(heightRange);

        Page<Person> page = personRepository.queryByExampleWithRange(personExample,ranges,pageable);

        return new ResponseEntity<Page<Person>>(page,HttpStatus.OK);

    }
}

Source address: https://github.com/wiselyman/query_with_example_and_range

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326403410&siteId=291194637