Spring 引导 JPA - 分页和排序

Spring Boot JPA - paging and sorting

我正在尝试在 Spring 引导中对我的 Spring Data JPA 存储库实施分页,但是当 运行 uni 测试时我遇到以下异常:

org.springframework.web.util.NestedServletException: Request processing failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.data.domain.Pageable]: Specified class is an interface
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:982)
...

有人可以指出我在这里缺少什么吗?这是我的存储库:

@Repository
public interface VenueRepository extends PagingAndSortingRepository<Venue, Long> {

    public Page<Venue> findAll(Pageable pageable);

}

和控制器:

@RestController
@RequestMapping("/venues")
public class VenueController {

    @Autowired
    private VenueRepository venueRepo;

    @RequestMapping(method = RequestMethod.GET)
    public ResponseEntity<Page<Venue>> getVenues(Pageable pageable) {
        return new ResponseEntity<>(venueRepo.findAll(pageable), HttpStatus.OK);
    }
}

最后是我的测试:

@Test
public void responseOkVenuesTest() throws Exception {
    mvc.perform(get("/venues").accept(MediaType.APPLICATION_JSON_VALUE)).andExpect(status().isOk());
}

我花了几个小时试图完成这项工作,但 运行 没有想法。感谢您提供任何提示!

更改您的方法 getVenues 以传递参数来实例化 PageRequest 而不是传递 Pageable :

 @RequestMapping(method = RequestMethod.GET)
  public ResponseEntity<List<Venue>> getVenues(int from,int to) {
     return new ResponseEntity<>(
        venueRepo.findAll((new PageRequest(from, to)), HttpStatus.OK).getContent();
  }

除了@SEY_91's answer you might also like to use the following solution inspired with How to remove redundant Spring MVC method by providing POST-only @Valid? 并在我的 Spring 引导驱动应用程序中使用了很长时间。

简而言之,这里有一个注解控制器方法参数的注解:

@Target(PARAMETER)
@Retention(RUNTIME)
public @interface PlainModelAttribute {
}

现在,只是一个方法处理器,它会扫描用 @PlainModelAttribute:

注释的参数
public final class PlainModelAttributeMethodProcessor
        extends ModelAttributeMethodProcessor {

    private final Map<TypeToken<?>, Converter<? super NativeWebRequest, ?>> index;

    private PlainModelAttributeMethodProcessor(final Map<TypeToken<?>, Converter<? super NativeWebRequest, ?>> index) {
        super(true);
        this.index = index;
    }

    public static HandlerMethodArgumentResolver plainModelAttributeMethodProcessor(final Map<TypeToken<?>, Converter<? super NativeWebRequest, ?>> index) {
        return new PlainModelAttributeMethodProcessor(index);
    }

    @Override
    public boolean supportsParameter(final MethodParameter parameter) {
        return parameter.hasParameterAnnotation(PlainModelAttribute.class) || super.supportsParameter(parameter);
    }

    @Override
    protected Object createAttribute(final String attributeName, final MethodParameter parameter, final WebDataBinderFactory binderFactory,
            final NativeWebRequest request) {
        final TypeToken<?> typeToken = TypeToken.of(parameter.getGenericParameterType());
        final Converter<? super NativeWebRequest, ?> converter = index.get(typeToken);
        if ( converter == null ) {
            throw new IllegalArgumentException("Cannot find a converter for " + typeToken.getType());
        }
        return converter.convert(request);
    }

    @Override
    protected void bindRequestParameters(final WebDataBinder binder, final NativeWebRequest request) {
        final HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);
        if ( !isSafe(resolve(servletRequest.getMethod())) ) {
            ((ServletRequestDataBinder) binder).bind(servletRequest);
        }
    }

    private static HttpMethod resolve(final String name) {
        return HttpMethod.valueOf(name.toUpperCase());
    }

    private static boolean isSafe(final HttpMethod method)
            throws UnsupportedOperationException {
        switch ( method ) {
        case GET:
        case HEAD:
        case OPTIONS:
            return true;
        case POST:
        case PUT:
        case PATCH:
        case DELETE:
            return false;
        case TRACE:
            throw new UnsupportedOperationException();
        default:
            throw new AssertionError(method);
        }
    }

}

我不太记得了,但是 resolve() 等效方法应该存在于 Spring Framework 的某个地方。请注意,我使用 Google Guava TypeToken 是为了让处理器与通用类型兼容(因为我在控制器中使用 IQuery<Foo>IQuery<Bar> 等模型)。现在只需注册处理器:

@Configuration
@EnableWebMvc
public class MvcConfiguration
        extends WebMvcConfigurerAdapter {

    @Override
    public void addArgumentResolvers(final List<HandlerMethodArgumentResolver> argumentResolvers) {
        argumentResolvers.add(createModelAttributeMethodProcessor());
    }

    private static HandlerMethodArgumentResolver createModelAttributeMethodProcessor() {
        return plainModelAttributeMethodProcessor(ImmutableMap.of(pageableTypeToken, MvcConfiguration::toPageable));
    }

    private static final TypeToken<Pageable> pageableTypeToken = new TypeToken<Pageable>() {
    };

    private static Pageable toPageable(final WebRequest request) {
        return new PageRequest(
                ofNullable(request.getParameter("page")).map(Integer::parseInt).orElse(0),
                ofNullable(request.getParameter("size")).map(Integer::parseInt).orElse(1)
        );
    }

}

这是对 Pageable DTO 转换的 Web 请求,转换器必须注册为参数解析器。现在可以使用了:

@RestController
@RequestMapping("/")
public class Controller {

    @RequestMapping(method = GET)
    public String get(@PlainModelAttribute final Pageable pageable) {
        return toStringHelper(pageable)
                .add("offset", pageable.getOffset())
                .add("pageNumber", pageable.getPageNumber())
                .add("pageSize", pageable.getPageSize())
                .add("sort", pageable.getSort())
                .toString();
    }

}

举几个例子:

  • /PageRequest{offset=0, pageNumber=0, pageSize=1, sort=null}
  • /?page=43PageRequest{offset=43, pageNumber=43, pageSize=1, sort=null}
  • /?size=32PageRequest{offset=0, pageNumber=0, pageSize=32, sort=null}
  • /?page=22&size=32PageRequest{offset=704, pageNumber=22, pageSize=32, sort=null}