k_o_
k_o_

Reputation: 6288

overwrite findAll() method of QuerydslPredicateExecutor

My goal is to add a dynamic Predicate to the findAll method of QuerydslPredicateExecutor. This should be used to filter entities based on the organization of the currently active user.

I'm using Spring Data together with Spring Data REST to get the REST API out of the box, i.e. I have no dedicated REST service where I can intercept the incoming data and modify it.

By extending a SimpleJpaRepository and registering it with @EnableJpaRepositories it is possible to overwrite a method and change its default behavior. I wanted to do this, but my Repository interfaces are implementing QuerydslPredicateExecutor and this does not seem to work.

My failed approach started as:

public class CustomizedJpaRepositoryIml<T, ID extends Serializable> extends
    SimpleJpaRepository<T, ID> {

    private EntityManager entityManager;

    @Autowired
    public CustomizedJpaRepositoryIml(JpaEntityInformation<T, ?> 
entityInformation,
                                  EntityManager entityManager) {
        super(entityInformation, entityManager);
        this.entityManager = entityManager;
    }

}

but obviously this extension does not provide the method to be overwritten. I debugged how the implementing QuerydslJpaPredicateExecutor is wired, but this is rather complex and I see no way of plugging in here something easily.

Another idea was to use a filter intercepting the URL call and adding parameters but this does not sound nice. I could also override the controller path for the finder with a @BasePathAwareController, but this would mean to do this for all entities I have and not in a single place.

Any ideas to achieve my goal? maybe there are also completely different options possible to achieve my goal of add additional filtering to the Querydsl Predicate

Upvotes: 3

Views: 3034

Answers (1)

k_o_
k_o_

Reputation: 6288

I found a way in the meanwhile. It requires to provide an own implementation of QuerydslPredicateExecutor. But this is not made easy in Spring Data. The answer is motivated by https://stackoverflow.com/a/53960209/3351474, but in the meanwhile a constructor has changed in newer Spring Data, why this cannot be taken 1:1.

I use a different example as in my question, but with this solution you have complete freedom also to add and append any Predicate. As an example I take here a customized Querydsl implementation using always the creationDate of an entity as sort criteria if nothing is is passed. I assume in this example that this column exists in some @MappedSuperClass for all entities. Use generated static metadata in real life instead the hard coded string "creationDate".

As first the wrapped delegating all CustomQuerydslJpaRepositoryIml delegating all methods to the QuerydslJpaPredicateExecutor:

/**
 * Customized Querydsl JPA repository to apply custom filtering and sorting logic.
 *
 */
public class CustomQuerydslJpaRepositoryIml<T> implements QuerydslPredicateExecutor<T> {

    private final QuerydslJpaPredicateExecutor querydslPredicateExecutor;

    public CustomQuerydslJpaRepositoryIml(QuerydslJpaPredicateExecutor querydslPredicateExecutor) {
        this.querydslPredicateExecutor = querydslPredicateExecutor;
    }

    private Sort applyDefaultOrder(Sort sort) {
        if (sort.isUnsorted()) {
            return Sort.by("creationDate").ascending();
        }
        return sort;
    }

    private Pageable applyDefaultOrder(Pageable pageable) {
        if (pageable.getSort().isUnsorted()) {
            Sort defaultSort = Sort.by(AuditableEntity_.CREATION_DATE).ascending();
            pageable = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), defaultSort);
        }
        return pageable;
    }

    @Override
    public Optional<T> findOne(Predicate predicate) {
        return querydslPredicateExecutor.findOne(predicate);
    }

    @Override
    public List<T> findAll(Predicate predicate) {
        return querydslPredicateExecutor.findAll(predicate);
    }

    @Override
    public List<T> findAll(Predicate predicate, Sort sort) {
        return querydslPredicateExecutor.findAll(predicate, applyDefaultOrder(sort));
    }

    @Override
    public List<T> findAll(Predicate predicate, OrderSpecifier<?>... orders) {
        return querydslPredicateExecutor.findAll(predicate, orders);
    }

    @Override
    public List<T> findAll(OrderSpecifier<?>... orders) {
        return querydslPredicateExecutor.findAll(orders);
    }

    @Override
    public Page<T> findAll(Predicate predicate, Pageable pageable) {
        return querydslPredicateExecutor.findAll(predicate, applyDefaultOrder(pageable));
    }

    @Override
    public long count(Predicate predicate) {
        return querydslPredicateExecutor.count(predicate);
    }

    @Override
    public boolean exists(Predicate predicate) {
        return querydslPredicateExecutor.exists(predicate);
    }
}

Next the CustomJpaRepositoryFactory doing the magic and providing the Querydsl wrapper class instead of the default one. The default one is passed as parameter and wrapped.

/**
 * Custom JpaRepositoryFactory allowing to support a custom QuerydslJpaRepository.
 *
 */
public class CustomJpaRepositoryFactory extends JpaRepositoryFactory {

    /**
     * Creates a new {@link JpaRepositoryFactory}.
     *
     * @param entityManager must not be {@literal null}
     */
    public CustomJpaRepositoryFactory(EntityManager entityManager) {
        super(entityManager);
    }

    @Override
    protected RepositoryComposition.RepositoryFragments getRepositoryFragments(RepositoryMetadata metadata) {
        final RepositoryComposition.RepositoryFragments[] modifiedFragments = {RepositoryComposition.RepositoryFragments.empty()};
        RepositoryComposition.RepositoryFragments fragments = super.getRepositoryFragments(metadata);
        // because QuerydslJpaPredicateExecutor is using som internal classes only a wrapper can be used.
        fragments.stream().forEach(
                f -> {
                    if (f.getImplementation().isPresent() &&
                            QuerydslJpaPredicateExecutor.class.isAssignableFrom(f.getImplementation().get().getClass())) {
                        modifiedFragments[0] = modifiedFragments[0].append(RepositoryFragment.implemented(
                                new CustomQuerydslJpaRepositoryIml((QuerydslJpaPredicateExecutor) f.getImplementation().get())));
                    } else {
                        modifiedFragments[0].append(f);
                    }
                }
        );
        return modifiedFragments[0];
    }
}

Finally the CustomJpaRepositoryFactoryBean. This must be registered with the Spring Boot application, to make Spring aware where to get the repository implementations from, e.g. with:

@SpringBootApplication
@EnableJpaRepositories(basePackages = "your.package",
        repositoryFactoryBeanClass = CustomJpaRepositoryFactoryBean.class)
...

Here now the class:

public class CustomJpaRepositoryFactoryBean<T extends Repository<S, I>, S, I> extends JpaRepositoryFactoryBean<T, S, I> {

    /**
     * Creates a new {@link JpaRepositoryFactoryBean} for the given repository interface.
     *
     * @param repositoryInterface must not be {@literal null}.
     */
    public CustomJpaRepositoryFactoryBean(Class<? extends T> repositoryInterface) {
        super(repositoryInterface);
    }

    protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager) {
        return new CustomJpaRepositoryFactory(entityManager);
    }
}

Upvotes: 3

Related Questions