19

Is it possible to allow query method @Params to be optional, specifically in the case of Spring Data REST?

For example, I'd like to bind a very similar search to the same resource path. To do this now, I would need something like the following:

@RestResource(path = "driver", rel = "byDriver")
List<Bar> findByDriverId(@Param("id") String id, Pageable pageable);

@RestResource(path = "driverAndSpan", rel = "byDriverAndSpan")
List<Bar> findByDriverIdAndStartTimeGreaterThanEqualAndEndTimeLessThanEqual(@Param("id") String id, @Param("start") Date start,
        @Param("end") Date end, Pageable pageable);

Which gives me:

byDriver: {
  href: "http://localhost:8080/foo/search/driver{?id,page,size,sort}",
},
byDriverAndSpan: {
  href: "http://localhost:8080/foo/search/driverAndSpan{?id,start,end,page,size,sort}",
}

What I want is to be able to see something like the following path, where start and end are optional parameters, rather than defining multiple methods in my Repository.

byDriverAndSpan: {
  href: "http://localhost:8080/foo/search/driverAndSpan{?id,*start,*end,page,size,sort}",
}

Which could potentially look like:

@RestResource(path = "driverAndSpan", rel = "byDriverAndSpan")
List<Bar> findByDriverIdAndStartTimeGreaterThanEqualAndEndTimeLessThanEqual(@Param("id") String id, @Param(value = "start", optional = true) Date start,
        @Param(value = "end", optional = true) Date end, Pageable pageable);
bvulaj
  • 5,023
  • 5
  • 31
  • 45
  • Digging out an old post... I'm surprised no one mentioned [Querydsl](http://www.querydsl.com/), which is exactly what you could have used. – Marc Tarin Apr 07 '17 at 08:32

5 Answers5

1

Maybe you can define a 'main' function as default which will delegate to the other functions. Something like this

@RestResource(path = "driverAndSpan", rel = "byDriverAndSpan")
default List<Bar> findByDriverIdAndOptionalStartTimeGreaterThanEqualAndOptionalEndTimeLessThanEqual(@Param("id") String id, @Param(value = "start", optional = true) Date start,
        @Param(value = "end", optional = true) Date end, Pageable pageable) {
   if(start != null && end != null) {
      return findByDriverIdAndStartTimeGreaterThanEqualAndEndTimeLessThanEqual(id, start, end, pageable);
   }
   return findByDriverId(id, pageable);
}

I think you can even use Optional as a parameter type, then you can use function overloading

@RestResource(path = "driverAndSpan", rel = "byDriverAndSpan")
default List<Bar> findByDriverIdAndStartTimeGreaterThanEqualAndEndTimeLessThanEqual(
@Param("id") String id, @Param(value = "start") Optional<Date> start,
        @Param(value = "end") Optional<Date> end, Pageable pageable) {
   if (!start.isEmpty() && !end.isEmpty()) {
      return findByDriverIdAndStartTimeGreaterThanEqualAndEndTimeLessThanEqual(id, start.get(), end.get(), pageable);
   }
   return findByDriverId(id, pageable);
}

List<Bar> findByDriverIdAndStartTimeGreaterThanEqualAndEndTimeLessThanEqual(String id, Date start, Date end, Pageable pageable);

Notice that in this case, you shouldn't expose the other endpoints, and only expose this default method.

Ahmed Sayed
  • 1,429
  • 12
  • 12
0

No, this is currently not supported. If it was there would be the risk of ambiguity. There could be the scenario where the incoming url could match more than 1 @RestResource.

To explain from a Java point of view lets say we could define two methods:

getPerson(String firstName, int age);

getPerson(String firstName, {Optional} int age, int phoneNumber);

There would be issues when someone is aiming to invoke the second method, not supplying age but being mapped to the first method with the phoneNumber being read as an age.

UserF40
  • 3,533
  • 2
  • 23
  • 34
  • How come is there risk of ambiguity? Parameters have (unique) names. – Nefreo Jan 11 '17 at 22:35
  • Are you supplying the parameter names in the request? – UserF40 Jan 12 '17 at 11:19
  • 3
    The OP states that he would like something along the lines of `http://localhost:8080/foo/search/driverAndSpan{?id,*start,*end,page,size,sort}`. In this case parameters are supplied by name so there shouldn't be room for ambiguity. – Nefreo Jan 12 '17 at 14:24
0

As UserF40 said, this isn't supported. We faced a similar issue and solved it by dynamically building queries depending on which Optional parameters were supplied.

You can use the Criteria API to achieve this, or by dynamically building the SQL in another service class by checking which parameters are present.

0

Use org.springframework.data.jpa.repository.JpaSpecificationExecutor;`

Step 1: Implement JpaSpecificationExecutor in your JPA Repository

Ex:

public interface TicketRepo extends JpaRepository<Ticket, Long>, JpaSpecificationExecutor<Ticket> {

Step 2 Now to fetch tickets based on optional parameters you can build Specification query using CriteriaBuilder

Ex:

public Specification<Ticket> getTicketQuery(Integer domainId, Calendar startDate, Calendar endDate, Integer gameId, Integer drawId) {
    return (root, query, criteriaBuilder) -> {
        List<Predicate> predicates = new ArrayList<>();

        predicates.add(criteriaBuilder.equal(root.get("domainId"), domainId));
        predicates.add(criteriaBuilder.greaterThanOrEqualTo(root.get("createdAt"), startDate));
        predicates.add(criteriaBuilder.lessThanOrEqualTo(root.get("createdAt"), endDate));

        if (gameId != null) {
            predicates.add(criteriaBuilder.equal(root.get("gameId"), gameId));
        }

        return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
    };
}

Step 3: Pass the Specification instance to jpaRepo.findAll(specification), it will return you the list of your entity object (Tickets here in the running example)

ticketRepo.findAll(specification); // Pass output of function in step 2 to findAll
Pankaj Garg
  • 1,272
  • 15
  • 21
-1

There is a "required" parameter option in @Param that you can use.

@RestResource(path = "driverAndSpan", rel = "byDriverAndSpan")

List findByDriverIdAndStartTimeGreaterThanEqualAndEndTimeLessThanEqual(@Param("id") String id, @Param(value = "start", required = false) Date start, @Param(value = "end", required = false) Date end, Pageable pageable);

iamrohit
  • 1
  • 1
  • 2
    There is no option `required` in `@Param` annotation. Only `@RequestParam` has a `required` option. – CDT Apr 26 '19 at 12:17