如何实现Pageable查询获取全部行?Driver Referral Lead分页漏处理问题
解决Pageable分页查询无法获取全部符合条件记录的问题
问题场景
现有需求是批量更新source_channel为SourceChannels.DRIVER_REFERRAL_CHANNEL的Lead记录,但当前实现仅能处理4条符合条件的记录(实际存在6条)。分页查询的第二次迭代无法获取到数据,日志显示空结果。
原实现代码
int batchSize = 2; Page<Lead> driverReferralLeads = repository.findByChannelAndCreatedAtIsLessThanAndDriverIdIsNullAndStatusIn(SourceChannels.DRIVER_REFERRAL_CHANNEL, expiryDate, statusForExpiring, PageRequest.of(0, batchSize)); List<Lead> driverReferralLeadsList = driverReferralLeads.getContent(); LOGGER.info("{} phoneNumbers: {}", SourceChannels.DRIVER_REFERRAL_CHANNEL, driverReferralLeadsList.stream().map(Lead::getPhone).toArray()); // todo to be removed service.expireLeads(SourceChannels.DRIVER_REFERRAL_CHANNEL, driverReferralLeadsList, expiryDate); int driverReferralLeadsIteration = 0; // todo to be removed while (driverReferralLeads.hasNext()) { LOGGER.info("{} iteration [{}].", SourceChannels.DRIVER_REFERRAL_CHANNEL, driverReferralLeadsIteration); // todo to be removed driverReferralLeads = repository.findByChannelAndCreatedAtIsLessThanAndDriverIdIsNullAndStatusIn(SourceChannels.DRIVER_REFERRAL_CHANNEL, expiryDate, statusForExpiring, driverReferralLeads.nextPageable()); List<Lead> driverReferralLeadsNextList = driverReferralLeads.getContent(); LOGGER.info("{} iteration [{}]. phoneNumbers: {}", SourceChannels.DRIVER_REFERRAL_CHANNEL, driverReferralLeadsIteration, driverReferralLeadsNextList.stream().map(Lead::getPhone).toArray()); // todo to be removed cartHeroService.expireLeads(SourceChannels.DRIVER_REFERRAL_CHANNEL, driverReferralLeadsNextList, expiryDate); driverReferralLeadsIteration++; // todo to be removed } LOGGER.info("{} Expired [{}] Leads.", LEAD_EXPIRY_LOGGER, SourceChannels.DRIVER_REFERRAL_CHANNEL);
日志信息
Driver Referral iteration [0]. phoneNumbers: [1122334455, 1123455551] Driver Referral iteration [1]. phoneNumbers: []
问题根源
核心原因是分页查询的条件与数据更新操作冲突:
- 第一次查询获取前2条符合条件的记录后,调用
expireLeads修改了这些记录的状态(使其不再满足statusForExpiring的条件)。 - 第二次分页查询时,数据库中符合原条件的记录只剩下4条,但分页请求的是第2页(页码从0开始,
nextPageable()返回页码1),此时数据库中第2页没有符合条件的数据。 - 第一次查询的
Page对象判断hasNext()为true(因为初始总记录数是6,总页数3),导致进入循环,但实际查询不到数据,后续第三页的查询也不会触发,最终漏处理了原本属于第3页的2条记录。
修复方案
方案一:先查询所有符合条件的ID,再分批处理(推荐)
先一次性获取所有符合条件的记录ID,再分批根据ID查询并更新,避免分页过程中数据状态变化导致的漏查。
步骤1:在Repository中添加查询ID的方法
// LeadRepository.java List<Long> findIdsByChannelAndCreatedAtIsLessThanAndDriverIdIsNullAndStatusIn( SourceChannels channel, Date expiryDate, List<LeadStatus> statuses);
步骤2:修改业务代码
// 获取所有符合条件的Lead ID List<Long> leadIds = repository.findIdsByChannelAndCreatedAtIsLessThanAndDriverIdIsNullAndStatusIn( SourceChannels.DRIVER_REFERRAL_CHANNEL, expiryDate, statusForExpiring); int batchSize = 2; int totalProcessed = 0; // 分批处理ID列表 for (int i = 0; i < leadIds.size(); i += batchSize) { int endIndex = Math.min(i + batchSize, leadIds.size()); List<Long> batchIds = leadIds.subList(i, endIndex); // 根据ID查询批次记录 List<Lead> batchLeads = repository.findAllById(batchIds); // 执行过期操作 service.expireLeads(SourceChannels.DRIVER_REFERRAL_CHANNEL, batchLeads, expiryDate); totalProcessed += batchLeads.size(); LOGGER.info("处理批次 {},手机号:{}", i/batchSize, batchLeads.stream().map(Lead::getPhone).toArray()); } LOGGER.info("{} 已过期 {} 条Leads。", LEAD_EXPIRY_LOGGER, SourceChannels.DRIVER_REFERRAL_CHANNEL, totalProcessed);
方案二:使用数据库快照隔离级别(可选)
如果必须保留分页查询的方式,可以配置数据库使用读快照隔离级别(如MySQL的REPEATABLE READ),让分页查询基于初始查询时的数据快照,不受后续更新操作的影响。但这种方式依赖数据库支持,且如果数据量较大,可能会占用更多数据库资源。
关键注意点
- 避免在分页查询过程中修改查询条件依赖的字段,否则会导致分页逻辑失效。
- 优先采用“先查ID再分批处理”的方式,逻辑更稳定,不受数据实时变化影响。
内容的提问来源于stack exchange,提问作者Dev
相关产品推荐
相关产品推荐

