iron24
iron24

Reputation: 141

Spring predicate multiple operators

I'm trying to make sortable/pageable/filterable repository with multiple filter methods. This is how my relevant code looks right now:

@Entity
@Table(name = "user")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(name = "name", length = 50, nullable = false)
private String name;

The repository:

@Repository
public interface UserRepository extends JpaRepository<User, Long> , 
QuerydslPredicateExecutor<User> {
}

And the controller:

@RequestMapping(path="/test")
public @ResponseBody ResponseEntity<Object> foo
( @QuerydslPredicate(root = User.class) Predicate predicate, Pageable pageable) {
    return userRepository.findAll(predicate,pageable);
}

It is working perfectly fine, like this: /users/?page=0&limit=1&sort=name,ASC&name=testuser But i can't use any other filter method except equals like "name=testuser" I was searching around and i keep finding guides like this but i'd have to write a PathBuilder for every entity and the controller looks way uglier too.

Is there a way to work around this and keep everything simplified like now? I need the basic operators like eq,neq,gte,lte,like, etc...

Upvotes: 4

Views: 2295

Answers (1)

Jose Anibal Rodriguez
Jose Anibal Rodriguez

Reputation: 429

Generally I use the CriteriaBuilder API. And it gives me a small solution, all you need to do is subscribe the repository to your custom spec.

public class CustomerSpecification implements Specification<CustomerDetail> {

    private C2Criteria criteria;

    public static CustomerSpecification of(C2Criteria criteria) {
        return new CustomerSpecification(criteria);
    }

    private CustomerSpecification(C2Criteria criteria) {
        this.criteria = criteria;
    }

    @Override
    public Predicate toPredicate
        (Root<CustomerDetail> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
    return getPredicate(root, builder, criteria);
   }
}

public <T> Predicate getPredicate(Root<T> root, CriteriaBuilder builder, C2Criteria criteria) {

    if (criteria.getOperation().equalsIgnoreCase(">")) {
        return builder.greaterThanOrEqualTo(
                root.get(criteria.getKey()), criteria.getValue().toString());
    } else if (criteria.getOperation().equalsIgnoreCase("<")) {
        return builder.lessThanOrEqualTo(
                root.get(criteria.getKey()), criteria.getValue().toString());
    } else if (criteria.getOperation().equalsIgnoreCase(":")) {
        if (root.get(criteria.getKey()).getJavaType().equals(String.class)) {
            return builder.like(
                    root.get(criteria.getKey()), "%" + criteria.getValue() + "%");
        } else {
            return builder.equal(root.get(criteria.getKey()), criteria.getValue());
        }
    }

And my criteria class is:

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class C2Criteria {
    private String key;
    private String operation = ":";
    private Object value;
}

And my JpaRepository looks like:

public interface CustomerDetailRepository extends JpaRepository<CustomerDetail, Long>, JpaSpecificationExecutor<CustomerDetail> {
}

In your controller you can use it by getting the object from the queryString.

@GetMapping(value = "renew")
public ResponseEntity renew(@NonNull PageDto page, @NonNull C2Criteria criteria) {
    Page<InsuranceRenew> renews = this.insuranceService.getRenew(page, criteria);
    return ResponseEntity.ok(renews);
}

and the insuranceservice method looks like:

@Override
public Page<InsuranceRenew> getRenew(@NonNull PageDto page, @NonNull C2Criteria criteria) {
    Pageable pageable = PageRequest.of(page.getPage(), page.getSize(), new Sort(page.getSort(), page.getOrderBy()));
    InsuranceRenewSpecification specification = InsuranceRenewSpecification.of(criteria);
    return this.renewRepository.findAll(specification, pageable);
}

You can see that I used a PageDto class, which is just a POJO with some fields for pagination purposes and it is defined as:

@Data
public class PageDto {
    private int page;
    private int size = 10;
    private Sort.Direction sort = Sort.Direction.DESC;
    private String orderBy = "id";
}

As you can see, I used to use the id as the default order by to prevent no wanted exceptions and de order DESC as default.

Hope it helps.

Upvotes: 2

Related Questions