3

I'm experimenting with Spring Data REST and so far it's going relatively well. I'm able to query and manipulate the entities, and I have reached a point where I'd like to filter the retrieved data by a variable number of parameters. For this purpose I've been reading and decided on QueryDSL which is integrated nicely with Spring, and it works (almost) flawlessly when using fields from the entities.

However, my filtering form contains some parameters which have no direct mapping to the entity, leading to this question. For the sake of brevity, I'll be using an over-simplified example, hence my using of a persons's age instead of birth-date & etc.

Supposing we have the following Person entity:

@Data
@NoArgsConstructor
@Entity
public class Person {
    @Id
    @GeneratedValue
    private UUID id;
    private String name;
    private String lastName;
    private Integer age;
}

... and the appropriate repo

@RepositoryRestResource
public interface PersonRepository extends CrudRepository<Person, UUID>, QuerydslPredicateExecutor<Person>, QuerydslBinderCustomizer<QPerson> {

    @RestResource
    Page<Person> findAll(@QuerydslPredicate Predicate predicate, Pageable pageable);

    @Override
    default void customize(QuerydslBindings bindings, QPerson person) {
        bindings.bind(String.class).first((SingleValueBinding<StringPath, String>) StringExpression::containsIgnoreCase);
    }
}

... one can access and filter persons by name or last name (case insensitive) via http://<server>/persons?name=whatever, so far so good.

Next step, I would like to see only the people that are "pensionable", let's say over 65 years old, so the URL would look like http://<server>/persons?pensionable=true. However, pensionable is not an attribute in the Person entity, so adding it as a request param doesn't do anything.

I've been trying to figure out how this can be achieved or if this is currently a limitation of the framework(s), but my searches haven't been successful so far. Eventually via trial and error, I've come up with something that seems to work but feels more like a hack:

Create a different PersonExtendedFilter bean (not entity) which includes the extra/arbitrary params:

@Data
@NoArgsConstructor
public class PersonExtendedFilter{
    private Boolean pensionable;
}

... create a BooleanPath using the above, and use it to define a binding inside the repo's customize method:

@Override
default void customize(QuerydslBindings bindings, QPerson person) {
    bindings.bind(String.class).first((SingleValueBinding<StringPath, String>) StringExpression::containsIgnoreCase);
    BooleanPath pensionable = new PathBuilder<>(PersonExtendedFilter.class, "personExtendedFilter").getBoolean("pensionable");
    bindings.bind(pensionable).first((path, value) -> new BooleanBuilder().and(value ? person.age.gt(65) : person.age.loe(65)));
}

Bottom line, I'm wondering whether there is an elegant way of doing this or if I missing something, be it from a logical POV, a RTFM one, or something else.

Morfic
  • 15,178
  • 3
  • 51
  • 61
  • 1
    My solution in the past has been to create a database view with these additional calculated fields and to use `@SecondaryTable` to map these fields to the entity. You can then sort and filter on these calculated fields as for any other fields. https://stackoverflow.com/a/40170121/1356423 – Alan Hay Dec 06 '19 at 09:44
  • 1
    I tried `Spring Data REST` and disliked it because of the issue you're having. It's minimal code / effort for the simple case but when you need more, it's not there for you. I'm not a fan of putting logic in the db, views etc. - it's hidden from the application developer and difficult to unit test. If you're going with `Spring Data REST`, I think you have a good solution. It was enough to put me off though. I opted for straight up JPA and a service layer and I feel it was the right choice - other requirements came in and I had the space to deliver them and test them in isolation. – Robert Bain Dec 17 '19 at 23:44

0 Answers0