Создайте ресурс коллекции с Sort и querydsl Predicate, но без Pageable

Я использую Spring data Rest и ищу способ создать ресурс коллекции (или поиска) с возможностями сортировки и предиката (с использованием querydsl), но без пейджера.

До сих пор мне нужно было только отключить пейджер, но у меня были возможности сортировки. Он хорошо работал, используя следующее и вызывая GET /userAccounts/search/noPager:

public interface ReportRepository 
    extends JpaRepository<Report, Integer>{

    /**
     * Non paged data with Sort capabilities
     */
    @RestResource(path="noPager")
    List<Report> findAllBy(Sort sort);

Теперь мне нужно добавить возможности предиката. Если я сделаю следующее:

public interface UserAccountRepository 
    extends JpaRepository<UserAccount, Integer>,
            QueryDslPredicateExecutor<UserAccount> {

    /**
     * Non paged data with Sort capabilities
     */
    @RestResource(path="noPager")
    List<UserAccount> findAllBy(Predicate predicate, Sort sort);

При вызове GET /userAccounts/search/noPager я получаю следующую ошибку:

java.lang.IllegalArgumentException: невозможно определить имена параметров для метода запроса fr.texsys.datemplus.dm.domain.data.UserAccountRepository.findAllBy! Используйте @Param или скомпилируйте с -параметрами в JDK 8. в org.springframework.data.repository.support.ReflectionRepositoryInvoker.prepareParameters (ReflectionRepositoryInvoker.java:235) ~ [spring-data-commons-1.12.3.RELEASE.jar! / : na] в org.springframework.data.repository.support.ReflectionRepositoryInvoker.invokeQueryMethod (ReflectionRepositoryInvoker.java:206) ~ [spring-data-commons-1.12.3.RELEASE.jar! /: na]

Что еще более странно, я попытался использовать QueryDSL с CrudRepository, у которого нет возможностей пейджера. Если я расширяю CrudRepository с помощью QueryDslPredicateExecutor<UserAccount> и вызываю ресурс коллекции GET /userAccounts, активируется пейджер.

Без QueryDslPredicateExecutor:

public interface UserAccountRepository 
    extends CrudRepository<UserAccount, Integer> {

Нет пейджера:

{
  "_embedded" : {
    "userAccounts" : [...]
  },
  "_links" : {
    "self" : {
      "href" : "http://localhost:15571/userAccounts"
    },
    "profile" : {
      "href" : "http://localhost:15571/profile/userAccounts"
    }
  }
}

Но с QueryDslPredicateExecutor:

public interface UserAccountRepository 
    extends CrudRepository<UserAccount, Integer>,
            QueryDslPredicateExecutor<UserAccount> {

Пейджер активирован

{
  "_embedded" : {
    "userAccounts" : [ ... ]
  },
  "_links" : {
    "self" : {
      "href" : "http://localhost:15571/userAccounts"
    },
    "profile" : {
      "href" : "http://localhost:15571/profile/userAccounts"
    }
  },
  "page" : {
    "size" : 20,
    "totalElements" : 3,
    "totalPages" : 1,
    "number" : 0
  }
}

person sgt-hartman    schedule 28.09.2016    source источник


Ответы (2)


Как насчет расширения JpaSpecificationExecutor, чтобы вы могли использовать Спецификация?

Создание собственной реализации спецификации позволит вам создать фильтр поиска и отсортировать его так, как вы хотите (путем доступа к CriteriaQuery).

Надеюсь, это поможет ...

person BenB    schedule 30.09.2016
comment
Я взглянул на API спецификаций. Сначала это выглядело многообещающе, но это не то, что мне нужно, потому что, используя Spring data rest, я не могу контролировать, какой метод listAll будет вызываться (он косвенно вызывается слоем spring data rest, когда HTTP-запрос выполняется в форме / api / {репозиторий}). Может, мне нужно создать своего собственного ‹Something› Executor, но я не знаю, как это сделать! - person sgt-hartman; 03.10.2016

Так что мне удалось сделать то, что я хочу, но это действительно обременительно, и надеюсь, что есть более легкий и удобный способ, который я не обнаружил:

1: Создайте собственный QuerydslRepositoryInvokerAdapter и направьте метод invokeFindAll(Pageable pageable) в findAll(Sort sort) executor:

public class CustomQuerydslRepositoryInvokerAdapter 
        extends QuerydslRepositoryInvokerAdapter {

    private final QueryDslPredicateExecutor<Object> executor;
    private final Predicate predicate;

    public CustomQuerydslRepositoryInvokerAdapter(
            RepositoryInvoker delegate, 
            QueryDslPredicateExecutor<Object> executor,
            Predicate predicate) {
        super(delegate, executor, predicate);

        Assert.notNull(delegate, "Delegate RepositoryInvoker must not be null!");
        Assert.notNull(executor, "QuerydslPredicateExecutor must not be null!");

        this.executor = executor;
        this.predicate = predicate;
    }

    @Override
    public Iterable<Object> invokeFindAll(Pageable pageable) {
        return executor.findAll(predicate, pageable.getSort());
    }

}

2: Создайте собственный RootResourceInformationHandlerMethodArgumentResolver:

public class CustomQuerydslAwareRootResourceInformationHandlerMethodArgumentResolver 
    extends RootResourceInformationHandlerMethodArgumentResolver {

    private final Repositories repositories;
    private final QuerydslPredicateBuilder predicateBuilder;
    private final QuerydslBindingsFactory factory;

    public CustomQuerydslAwareRootResourceInformationHandlerMethodArgumentResolver(Repositories repositories,
            RepositoryInvokerFactory invokerFactory, ResourceMetadataHandlerMethodArgumentResolver resourceMetadataResolver,
            QuerydslPredicateBuilder predicateBuilder, QuerydslBindingsFactory factory) {

        super(repositories, invokerFactory, resourceMetadataResolver);

        this.repositories = repositories;
        this.predicateBuilder = predicateBuilder;
        this.factory = factory;
    }

    /* 
     * (non-Javadoc)
     * @see org.springframework.data.rest.webmvc.config.RootResourceInformationHandlerMethodArgumentResolver#postProcess(org.springframework.data.repository.support.RepositoryInvoker, java.lang.Class, java.util.Map)
     */
    @Override
    @SuppressWarnings({ "unchecked" })
    protected RepositoryInvoker postProcess(MethodParameter parameter, RepositoryInvoker invoker,
            Class<?> domainType, Map<String, String[]> parameters) {

        Object repository = repositories.getRepositoryFor(domainType);

        if (!CustomQueryDslPredicateExecutor.class.isInstance(repository)
                || !parameter.hasParameterAnnotation(QuerydslPredicate.class)) {
            return invoker;
        }

        ClassTypeInformation<?> type = ClassTypeInformation.from(domainType);

        QuerydslBindings bindings = factory.createBindingsFor(null, type);
        Predicate predicate = predicateBuilder.getPredicate(type, toMultiValueMap(parameters), bindings);

        return new CustomQuerydslRepositoryInvokerAdapter(invoker, (QueryDslPredicateExecutor<Object>) repository, predicate);
    }

    private static MultiValueMap<String, String> toMultiValueMap(Map<String, String[]> source) {

        MultiValueMap<String, String> result = new LinkedMultiValueMap<String, String>();

        for (String key : source.keySet()) {
            result.put(key, Arrays.asList(source.get(key)));
        }

        return result;
    }


}

3: Перезаписать определение Bean-компонента RootResourceInformationHandlerMethodArgumentResolver:

@Configuration
public class CustomRepositoryRestMvcConfiguration extends RepositoryRestMvcConfiguration {

    @Autowired ApplicationContext applicationContext;

    @Override
    @Bean
    public RootResourceInformationHandlerMethodArgumentResolver repoRequestArgumentResolver() {

        if (QueryDslUtils.QUERY_DSL_PRESENT) {

            QuerydslBindingsFactory factory = applicationContext.getBean(QuerydslBindingsFactory.class);
            QuerydslPredicateBuilder predicateBuilder = new QuerydslPredicateBuilder(defaultConversionService(),
                    factory.getEntityPathResolver());

            return new CustomQuerydslAwareRootResourceInformationHandlerMethodArgumentResolver(repositories(),
                    repositoryInvokerFactory(defaultConversionService()), resourceMetadataHandlerMethodArgumentResolver(),
                    predicateBuilder, factory);
        }

        return new RootResourceInformationHandlerMethodArgumentResolver(repositories(),
                repositoryInvokerFactory(defaultConversionService()), resourceMetadataHandlerMethodArgumentResolver());
    }

}

4. Создайте собственный QueryDslPredicateExecutor:

public interface CustomQueryDslPredicateExecutor<T> extends QueryDslPredicateExecutor<T> {

    Iterable<T> findAll(Predicate predicate, Sort sort);

}

5: Затем примените его к репозиторию

public interface UserAccountRepository 
    extends CrudRepository<UserAccount, Integer>,
            CustomQueryDslPredicateExecutor<UserAccount> {
    [...] 
}

Ой! но работает ...

person sgt-hartman    schedule 03.10.2016