2

I am implementing a Repository which implements QuerydslBinderCustomizer to help create Predicates from the url filter parameters. It is as such

@NoRepositoryBean
public interface TableRepository<T extends TableEntity, S extends EntityPathBase<T>> extends PagingAndSortingRepository<T, UUID>, QuerydslPredicateExecutor<T>, QuerydslBinderCustomizer<S> {
    @Override
    default void customize(@NonNull QuerydslBindings bindings, @NonNull S root) {
        bindings.bind(String.class).all((MultiValueBinding<StringPath, String>) (path, values) -> {
            BooleanBuilder predicate = new BooleanBuilder();
            values.forEach(value -> predicate.or(path.containsIgnoreCase(value)));
            return Optional.of(predicate);
        });
    }
}

My individual entity repositories are extending from this like,

public interface UserRepository extends TableRepository<User, QUser> {
}

This gives rise to an endpoint http://localhost:port/users. Things are working fine and I can make requests such as http://localhost:port/users?name="somename".

Now, I am implementing soft deletes and I don't want to return soft deleted records on querying. I was planning on editing the customize method for this like

default void customize(@NonNull QuerydslBindings bindings, @NonNull S root) {
        bindings.bind(String.class).all((MultiValueBinding<StringPath, String>) (path, values) -> {
            BooleanBuilder predicate = new BooleanBuilder();
            values.forEach(value -> predicate.or(path.containsIgnoreCase(value)));
            // If path does not have delete flag, add deleted=false to the predicate
            return Optional.of(predicate);
        });
    }

But the issue arises when a call is made to the endpoint without any parameter i.e. at http://localhost:port/users. In this case bindings.bind() gets executed but BooleanBuilder predicate = new BooleanBuilder(); part is not. Thus I am unable to add the required predicate to filter out soft deleted records. How to proceed?

I might be doing the filtering of deleted records the wrong way. Is there a better alternative?

EDIT: As requested, I am linking a basic repository https://github.com/SayakMukhopadhyay/demo-querydsl

Sayak Mukhopadhyay
  • 1,332
  • 2
  • 19
  • 34
  • Looks like you want to override the findAll() method to exclude deleted entities. You can do this by various means. See here:https://stackoverflow.com/a/29034847/1356423 – Alan Hay Apr 18 '18 at 07:56
  • That is the first thing I tried. But it seems like after implementing Querydsl, the `CrudRepository` `findAll()` is never called. I tried by writing `@Override default Iterable findAll(){ System.out.print("yo"); return null; }` and it doesn't print nor breakpoint gets triggered. – Sayak Mukhopadhyay Apr 18 '18 at 08:04
  • Try via the other options referenced in the answer. – Alan Hay Apr 18 '18 at 08:31
  • I don't see how any of the answer gives a direction to my issue. I feel my issue is particular to querydsl which is not in the scope of the linked answer. Also I am using spring-data-rest so my repositories are automatically linked to REST calls. As such, creating custom methods in my repository is also not feasible. – Sayak Mukhopadhyay Apr 18 '18 at 08:43
  • Yes, I am perfectly aware of what you are doing - I can read (https://stackoverflow.com/tags/spring-data-rest/topusers). I have referred to it as it outlines 3 ways to override the findAll() method/query of a repository. I would expect that a REST request to the SDR framework delegates to findAll() in the event that no parameters are specified (i.e. where the QueryDSL bindings are not required) but I could be wrong on that. The answer I have referred you to gives 3 ways to override the query executed for a call to findAll() (e.g. tack on a condition to exclude deleted). – Alan Hay Apr 18 '18 at 09:06
  • The issue is that even when no parameters are specified `Page findAll(Predicate predicate, Pageable pageable)` gets called (I have verified this). Also, I attempted to do `@Override @Query("select e from #{#entityName} e where e.deleted=false") Page findAll(Predicate predicate, Pageable pageable);` to filter but as expected this causes the Predicate to be ignored. – Sayak Mukhopadhyay Apr 18 '18 at 09:21
  • Please check my [example](https://stackoverflow.com/a/48596145/5380322), may it will be helpful.. – Cepr0 Apr 18 '18 at 09:28
  • @Cepr0 The bindings in the `customize` method is already implemented by me. Thing is, these bindings work when parameters are passed. If no parameter are passed, the lambdas within the binds do no execute. I am looking for a way around this. – Sayak Mukhopadhyay Apr 18 '18 at 09:38
  • Hint from [I do ask a good question](https://stackoverflow.com/help/how-to-ask) : could you please provide a [minimal, complete and verifiable example](https://stackoverflow.com/help/mcve). A sample project would help us reproduce the issue without coding it ourselves. – Marc Tarin Apr 19 '18 at 09:42
  • @MarcTarin It didn't seem to me that creating a working example would be needed for this question. I have anyway created one. Hope this helps. – Sayak Mukhopadhyay Apr 19 '18 at 14:28
  • Hi, We are facing the exact same issue for the same requirement of soft delete. Basically we would like to set some of the predicate by default if they weren't set in the request params. what we don't want is to rebuild the whole Predicate. – js.palus May 13 '21 at 16:52

1 Answers1

2

So I imagine that you found a solution by now. But for those who try to get some sort of "default predicate" here is a potential solution.

Just like in your case, we also had a soft delete and wanted to exclude them by default unless specifically requested.

First of all, you can't set this in the customize method. these binding only trigger when there is a value passed for a criteria. For some reasons they didn't provide a bindings for when there is no value. they probably had good reasons to do this way. So in this method you only care about the criteria passed.

The way we handled default values in our request was to modify the Predicate before calling findAll.

The Controller

The Controller signature should look like this, with a predicate:

@GetMapping
public ResponseEntity<List<User>> searchUsers(
            @QuerydslPredicate(root = User.class, bindings = UserRepository.class) Predicate predicate) 

The Service

All you need is to check if the predicate has the criteria in it, and if not then add it yourself.

if (!ExpressionUtils.extract(predicate).toString().contains("deleted =")) {
    predicate = new StringPath("deleted").eq("false").and(predicate);
}
userRepository.findAll(predicate);

That should add the criteria if not there and ignore if it is.

This is obviuosly a simplified representation but the idea is there.

Hope this helps.

js.palus
  • 83
  • 1
  • 10