You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JPA查询分页返回每页元素数量不符问题排查

JPA分页返回数量不符的问题排查与解决

问题根源

你的多表JOIN查询产生了重复结果行(比如一个用户对应多条KYC/钱包记录),setFirstResult和setMaxResults是基于数据库返回的原始行数分页,而非去重后的DTO数量。当你设置页大小10时,数据库返回的前10行里包含重复数据,最终转换为DTO后只剩8个唯一条目,导致显示数量不符。

解决方案

方案1:先分页查询主表ID,再关联查详情

先获取分页后的用户ID列表,再根据ID查询完整DTO,避免关联导致的分页混乱:

// 第一步:分页查询customerId列表
Query idQuery = entityManager.createQuery("SELECT cp.customerId FROM CustomerProfile cp " +
        "JOIN CustomerAuthModel ca ON cp.customerId=ca.customerId " +
        "JOIN CustomerWalletModel cw ON cp.customerId = cw.customerId " +
        "JOIN CustomerKycModel ck ON ck.customerId = cw.customerId " +
        "ORDER BY ck.creationDateTime DESC");
idQuery.setFirstResult((pageNumber-1)*pageSize);
idQuery.setMaxResults(pageSize);
List<Long> customerIds = idQuery.getResultList();

// 第二步:根据ID查询完整DTO
List<CustomerReportResponseDTO> customerReportLst = Collections.emptyList();
if(!customerIds.isEmpty()){
    Query dtoQuery = entityManager.createQuery("SELECT new com.one97.one97pay.web.dto.CustomerReportResponseDTO(" +
            "cw.walletId, cw.balance, cw.isEnabled,ca.isEnabled,ca.accountLockType," +
            "ck.isEnabled AS kycStatus,cp.mobile,cp.residentId,cp.ridExpiryDate, ck.kycStatus,ck.kycType,ca.createdOn," +
            "TRIM(UPPER(CONCAT (cp.firstName,CASE WHEN cp.middelName is NULL THEN '' ELSE CONCAT(' ', cp.middelName) END," +
            "CASE WHEN cp.lastName is NULL THEN '' ELSE CONCAT(' ', cp.lastName) END ))), ck.creationDateTime)" +
            " FROM CustomerProfile cp JOIN CustomerAuthModel ca ON cp.customerId=ca.customerId " +
            "JOIN CustomerWalletModel cw ON cp.customerId = cw.customerId " +
            "JOIN CustomerKycModel ck ON ck.customerId = cw.customerId " +
            "WHERE cp.customerId IN :customerIds " +
            "ORDER BY ck.creationDateTime DESC");
    dtoQuery.setParameter("customerIds", customerIds);
    customerReportLst = dtoQuery.getResultList();
}

方案2:使用DISTINCT去重后分页

在SELECT语句中添加DISTINCT关键字,确保数据库返回唯一行,再进行分页。注意需要保证DTO的equals()和hashCode()方法正确实现,或者数据库层面能识别重复行:

Query query = entityManager.createQuery("SELECT DISTINCT new com.one97.one97pay.web.dto.CustomerReportResponseDTO(" +
        "cw.walletId, cw.balance, cw.isEnabled,ca.isEnabled,ca.accountLockType," +
        "ck.isEnabled AS kycStatus,cp.mobile,cp.residentId,cp.ridExpiryDate, ck.kycStatus,ck.kycType,ca.createdOn," +
        "TRIM(UPPER(CONCAT (cp.firstName,CASE WHEN cp.middelName is NULL THEN '' ELSE CONCAT(' ', cp.middelName) END," +
        "CASE WHEN cp.lastName is NULL THEN '' ELSE CONCAT(' ', cp.lastName) END ))), ck.creationDateTime)" +
        " FROM CustomerProfile cp JOIN CustomerAuthModel ca ON cp.customerId=ca.customerId " +
        "JOIN CustomerWalletModel cw ON cp.customerId = cw.customerId " +
        "JOIN CustomerKycModel ck ON ck.customerId = cw.customerId" +
        " ORDER BY ck.creationDateTime DESC");
int pageSize = requestDto.getPageSize();
int pageNumber = requestDto.getPageNum();
query.setFirstResult((pageNumber-1) * pageSize); 
query.setMaxResults(pageSize);
List<CustomerReportResponseDTO> customerReportLst = query.getResultList();

方案3:使用Spring Data JPA的Pageable接口

如果项目使用Spring Data JPA,直接用Pageable和Page接口,框架会自动处理分页逻辑:

  1. 定义Repository方法:
@Repository
public interface CustomerProfileRepository extends JpaRepository<CustomerProfile, Long> {
    @Query("SELECT DISTINCT new com.one97.one97pay.web.dto.CustomerReportResponseDTO(" +
            "cw.walletId, cw.balance, cw.isEnabled,ca.isEnabled,ca.accountLockType," +
            "ck.isEnabled AS kycStatus,cp.mobile,cp.residentId,cp.ridExpiryDate, ck.kycStatus,ck.kycType,ca.createdOn," +
            "TRIM(UPPER(CONCAT (cp.firstName,CASE WHEN cp.middelName is NULL THEN '' ELSE CONCAT(' ', cp.middelName) END," +
            "CASE WHEN cp.lastName is NULL THEN '' ELSE CONCAT(' ', cp.lastName) END ))), ck.creationDateTime)" +
            " FROM CustomerProfile cp JOIN CustomerAuthModel ca ON cp.customerId=ca.customerId " +
            "JOIN CustomerWalletModel cw ON cp.customerId = cw.customerId " +
            "JOIN CustomerKycModel ck ON ck.customerId = cw.customerId" +
            " ORDER BY ck.creationDateTime DESC")
    Page<CustomerReportResponseDTO> findCustomerReports(Pageable pageable);
}
  1. 调用方法:
int pageSize = requestDto.getPageSize();
int pageNumber = requestDto.getPageNum();
Pageable pageable = PageRequest.of(pageNumber-1, pageSize, Sort.by(Sort.Direction.DESC, "ck.creationDateTime"));
Page<CustomerReportResponseDTO> page = customerProfileRepository.findCustomerReports(pageable);
List<CustomerReportResponseDTO> customerReportLst = page.getContent();

内容的提问来源于stack exchange,提问作者Ritvik Khanijo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 03:50:32