Spring GET API中Offset与Limit参数不生效问题及解决咨询
问题描述
我有一个Spring应用,GET API及Repository代码如下:
API代码
import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; @GetMapping(path = {"/v1/Log"}, produces = {"application/json"}, consumes = {"application/json"}) public ResponseEntity<LogsResponse> getLog(final Pageable pageable) { final Page<Log> log = LogService.getActionLog(pageable); return ResponseEntity.ok(log.getContent()); }
Repository代码
import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.stereotype.Repository; @Repository public interface LogDBRepository extends MongoRepository<Log, String> { Page<Log> findAll(final Pageable pageable); }
测试时发现:使用请求参数/log?offset=1&limit=1时,API未跳过第一条数据且未限制返回1条结果,而是返回MongoDB中所有文档;但使用/log?page=1&size=1时API正常工作。希望实现通过offset和limit参数跳过第一条数据并仅返回1条结果。
测试代码如下:
@Test public void givenPageRequest_whenGetLogCalled_returnSuccess() { LogDBRepository.saveAll(Arrays.asList(Log.builder().id("id1").transactionId("tx1").status(LogStatus.PENDING).build(), Log.builder().id("id2").status(LogStatus.PENDING).transactionId("tx2").build())); final HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); final HttpEntity httpEntity = new HttpEntity(headers); ResponseEntity<LogsResponse> responseEntity = testRestTemplate.exchange("/log?offset=1&limit=1", HttpMethod.GET, httpEntity, LogsResponse.class); Assertions.assertEquals(HttpStatus.OK.value(), responseEntity.getStatusCode().value()); }
解决方案
问题原因
Spring Data默认的Pageable参数绑定只识别page(页码)、size(每页条数)、sort这些参数,offset和limit不会自动映射到Pageable对象中。当传入offset和limit时,Pageable会使用默认值(page=0、size=20),导致Repository返回全量数据。
解决方法
方法一:手动接收参数并构建Pageable
直接在API方法中接收offset和limit参数,手动构造符合需求的Pageable实例:
@GetMapping(path = {"/v1/Log"}, produces = {"application/json"}, consumes = {"application/json"}) public ResponseEntity<LogsResponse> getLog( @RequestParam(value = "offset", required = false, defaultValue = "0") long offset, @RequestParam(value = "limit", required = false, defaultValue = "20") int limit) { // 构建基于偏移量的Pageable,跳过offset条数据,取limit条 Pageable pageable = PageRequest.ofSize(limit).withOffset(offset); final Page<Log> logPage = LogService.getActionLog(pageable); return ResponseEntity.ok(logPage.getContent()); }
方法二:配置Spring MVC支持offset/limit参数绑定
通过自定义PageableHandlerMethodArgumentResolver,让Spring自动将offset和limit参数映射到Pageable:
import org.springframework.context.annotation.Configuration; import org.springframework.data.web.PageableHandlerMethodArgumentResolver; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import java.util.List; @Configuration public class WebMvcConfig implements WebMvcConfigurer { @Override public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) { PageableHandlerMethodArgumentResolver pageableResolver = new PageableHandlerMethodArgumentResolver(); // 绑定offset参数到Pageable的偏移量 pageableResolver.setOffsetParameterName("offset"); // 绑定limit参数到Pageable的每页大小 pageableResolver.setSizeParameterName("limit"); // 如果你的offset是从1开始计数(比如offset=1代表跳过第一条),开启下面的配置 // pageableResolver.setOneIndexedParameters(true); resolvers.add(pageableResolver); WebMvcConfigurer.super.addArgumentResolvers(resolvers); } }
配置完成后,原API方法无需修改,直接传入offset和limit参数即可生效。
方法三:Repository层新增offset/limit方法(不推荐)
在Repository中直接定义接收offset和limit的方法,绕过Pageable:
@Repository public interface LogDBRepository extends MongoRepository<Log, String> { @Query("{}") List<Log> findAllByOffsetAndLimit(@Param("offset") int offset, @Param("limit") int limit); }
这种方法破坏了Spring Data的分页规范,仅在特殊场景下使用,推荐优先使用前两种方法。
测试验证
修改完成后,在测试方法中添加断言验证返回结果:
@Test public void givenPageRequest_whenGetLogCalled_returnSuccess() { // 初始化测试数据 LogDBRepository.saveAll(Arrays.asList( Log.builder().id("id1").transactionId("tx1").status(LogStatus.PENDING).build(), Log.builder().id("id2").status(LogStatus.PENDING).transactionId("tx2").build() )); final HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); final HttpEntity httpEntity = new HttpEntity(headers); ResponseEntity<LogsResponse> responseEntity = testRestTemplate.exchange( "/log?offset=1&limit=1", HttpMethod.GET, httpEntity, LogsResponse.class ); Assertions.assertEquals(HttpStatus.OK.value(), responseEntity.getStatusCode().value()); // 验证返回结果数量为1 Assertions.assertEquals(1, responseEntity.getBody().getLogs().size()); // 验证返回的是第二条数据 Assertions.assertEquals("tx2", responseEntity.getBody().getLogs().get(0).getTransactionId()); }
内容的提问来源于stack exchange,提问作者GenuineRR1
相关产品推荐
相关产品推荐

