实现Spring Scroll API滚动加载时,如何限制返回结果数量?
Spring Scroll API 实现滚动加载时限制返回结果数量
问题描述
我想要通过Spring的Scroll API实现前端滚动加载,每次请求加载新数据,但尝试了多种方式都无法限制返回的结果数量,始终返回全表数据:
- 使用PageRequest
// Service fun getOrders(customerId: Long): List<OrderDto> { val customer = customerRepository.findById(customerId).getOrElse { return emptyList() } val orders: WindowIterator<Order> = WindowIterator.of<Order> { position -> orderRepsitory.findAllByCustomerOrderByCreated( customer, position as KeysetScrollPosition?, PageRequest.of(0, 20) ) } .startingAt(ScrollPosition.keyset()) val ordersResult: MutableList<Orders> = ArrayList<Orders>() orders.forEachRemaining { ordersResult.add(it) } return ordersResult.map { OrderDto(...) } } // Order Respository interface OrderRepository : JpaRepository<Order, Long> { fun findAllByCustomerOrderByCreated( customer: Customer, position: KeysetScrollPosition?, pageable: Pageable, ): Window<Order> }
- 调整JPA查询为
findFirst5
// Order Respository interface OrderRepository : JpaRepository<Order, Long> { fun findFirst5ByCustomerOrderByCreated( customer: Customer, position: KeysetScrollPosition?, ): Window<Order> } // Service中的调用 orderRepsitory.findFirst5ByCustomerOrderByCreated( customer, position as KeysetScrollPosition? )
- 使用Limit参数
// Order Respository interface OrderRepository : JpaRepository<Order, Long> { fun findAllByCustomerOrderByCreated( customer: Customer, limit: Limit, ): Window<Order> } // Service中的调用 orderRepsitory.findAllByCustomerOrderByCreated( customer, position as KeysetScrollPosition?, Limit.of(20) )
请问如何通过Spring Scroll API正确限制每次返回的结果数量?
解决方案
核心问题分析
你之前的代码始终返回全表数据,根源在于Service层使用了WindowIterator.forEachRemaining()——这个方法会迭代所有滚动窗口,把符合条件的全量数据全部加载出来,直接抵消了限制结果数量的配置。
正确实现步骤
- 规范Repository方法定义
使用Spring Data的ScrollPosition和Limit构建查询,同时确保排序字段包含唯一标识(比如ID),避免滚动时出现数据重复或遗漏:
interface OrderRepository : JpaRepository<Order, Long> { // 排序字段需包含唯一键,保证滚动逻辑的一致性 fun findAllByCustomerOrderByCreatedDescIdDesc( customer: Customer, scrollPosition: ScrollPosition, limit: Limit ): Window<Order> }
- Service层单次请求仅获取一个窗口数据
不要遍历所有窗口,而是根据前端传递的滚动位置(首次请求用ScrollPosition.keyset()),获取单次请求的批次数据,并返回下一次滚动需要的位置给前端:
fun getOrders(customerId: Long, scrollPosition: ScrollPosition = ScrollPosition.keyset()): Pair<List<OrderDto>, ScrollPosition> { val customer = customerRepository.findById(customerId).getOrElse { return emptyList() to scrollPosition } // 单次请求仅获取20条数据的窗口 val window = orderRepository.findAllByCustomerOrderByCreatedDescIdDesc( customer, scrollPosition, Limit.of(20) ) val dtos = window.content.map { OrderDto(...) } // 返回当前批次数据和下一次滚动的起始位置 return dtos to window.scrollPosition }
- 前端交互逻辑配合
- 首次请求时,不传滚动位置(或传递初始的
KeysetScrollPosition) - 每次请求成功后,保存返回的
scrollPosition(可序列化后存在前端缓存) - 滚动到底部时,携带保存的
scrollPosition发起下一次请求,获取下一批数据
关键注意事项
Window对象包含当前批次的content数据,以及scrollPosition(用于下一次滚动的起始标记)- 排序字段必须包含唯一键,否则当存在相同
created时间的记录时,滚动会出现数据重复或遗漏 - 仅在需要全量加载数据时使用
WindowIterator的遍历方法,滚动加载场景下要避免全量遍历
内容的提问来源于stack exchange,提问作者four-eyes
相关产品推荐
相关产品推荐

