从 500 错误到真相:解密“分页查询失败:null”的幕后黑手 !!!

🚨 从 500 错误到真相:解密“分页查询失败:null”的幕后黑手 🌟

嘿,技术探险家们!👋 今天我们要破解一个让人头秃的 bug:一个分页查询接口,返回了神秘的 500 错误,提示 "分页查询失败:null"!😱 这篇文章将带你从现象入手,深入代码,揪出罪魁祸首,还有流程图助阵,快系好安全带,跟我一起 debug 吧!💪


🎯 第一幕:bug 的“犯罪现场”

问题复现

我在测试一个分页接口,输入是这样的 JSON:

{
  "field": null,
  "page": 0,
  "size": null,
  "value": null
}

结果,服务器甩给我这个:

{
  "code": 500,
  "msg": "分页查询失败:null",
  "data": null
}

啥?500 是服务器内部错误,但 "null" 是啥意思?没有具体线索,我一脸懵。🤦‍♂️


🔍 第二幕:嫌疑人登场

代码一览

先看看控制器:

@PostMapping("/listInviteCodeByPageWithSearch")
public BaseResult listInviteCodeByPageWithSearch(
        @SessionAttribute(value = Constants.ADMIN_ID) Integer adminId,
        @Valid @RequestBody PageWithSearch pageWithSearch) {
    try {
        adminId = adminCommonService.getVipIdByProduct(adminId);
        Page<InviteCode> inviteCodePage = inviteCodeService.findPaginatedInviteCodeByAdminIdAndSearch(adminId, pageWithSearch);
        return BaseResult.success(inviteCodePage);
    } catch (Exception e) {
        return BaseResult.failure(500, "分页查询失败:" + e.getMessage());
    }
}

服务层:

public Page<InviteCode> findPaginatedInviteCodeByAdminIdAndSearch(Integer adminId, PageWithSearch pageWithSearch) {
    PageRequest pageRequest = PageRequest.of(pageWithSearch.getPage(), pageWithSearch.getPageSize());
    String field = pageWithSearch.getField();
    String value = pageWithSearch.getValue();
    if (!StringUtils.isEmpty(field) && !StringUtils.isEmpty(value)) {
        return inviteCodeRepository.findPaginatedInviteCodeByAdminIdAndFieldAndValue(adminId, field, value, pageRequest);
    } else {
        return inviteCodeRepository.findByAdminId(adminId, pageRequest);
    }
}

PageWithSearch(关键部分):

public class PageWithSearch extends BasePage {
    private String field;
    private String value;
    public Integer getPageSize() {
        return this.size;
    }
    public Pageable toPageableWithDefault(Integer page, Integer size) {
        this.page = this.page == null ? page : this.page;
        this.size = this.size == null ? size : this.size;
        return PageRequest.of(this.page, this.size);
    }
}

BasePage(关键部分):

public class BasePage {
    Integer page;
    Integer size;
    public Integer getPage() {
        return page;
    }
    public Integer getSize() {
        return size;
    }
    public Pageable toPageable() {
        page = page != null ? page : 0;
        size = size != null ? size : 9999;
        return PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "createdDate"));
    }
}

初步猜测

  • 错误来源"分页查询失败:null" 来自 e.getMessage(),说明捕获了一个异常,但异常信息是 null
  • 嫌疑人size: null 很可疑,会不会是分页参数的问题?

🐞 第三幕:锁定真凶

线索分析

  • 输入处理
    • field: null, value: null → 进入 findByAdminId 分支。
    • page: 0, size: nullgetPage() 返回 0getPageSize() 返回 null
  • 关键代码
    PageRequest pageRequest = PageRequest.of(pageWithSearch.getPage(), pageWithSearch.getPageSize());
    
    • PageRequest.of(int page, int size) 需要 int
    • getPageSize() 返回 Integer,且是 null

真相揭晓!💡

  • 自动拆箱
    • getPageSize() 返回 nullInteger 类型)。
    • PageRequest.of 期望 int,Java 自动拆箱:null.intValue()
    • 调用 null 的方法 → NullPointerException
  • 异常处理
    • catch (Exception e) 捕获异常。
    • NullPointerExceptiongetMessage()null,拼接后变成 "分页查询失败:null"
Mermaid 流程图:错误发生过程
输入: size=null
getPageSize() 返回 null
PageRequest.of(0, size)
自动拆箱: size.intValue()
抛出 NullPointerException
catch 捕获, 返回 500: '分页查询失败:null'

🔧 第四幕:解决问题

为什么会这样?

  • PageWithSearch 的设计
    • getPageSize() 直接返回 this.size,没有防御 null
    • toPageableWithDefault 提供了默认值,但服务层没用。
  • BasePage 的设计
    • toPageable() 也有默认值(size=9999),但也没被调用。
  • 服务层的疏忽
    • 直接用 getPageSize(),没有检查 null

修复方案

方案 1:用 toPageableWithDefault

修改服务层:

public Page<InviteCode> findPaginatedInviteCodeByAdminIdAndSearch(Integer adminId, PageWithSearch pageWithSearch) {
    Pageable pageable = pageWithSearch.toPageableWithDefault(0, 10);
    String field = pageWithSearch.getField();
    String value = pageWithSearch.getValue();
    if (!StringUtils.isEmpty(field) && !StringUtils.isEmpty(value)) {
        return inviteCodeRepository.findPaginatedInviteCodeByAdminIdAndFieldAndValue(adminId, field, value, pageable);
    } else {
        return inviteCodeRepository.findByAdminId(adminId, pageable);
    }
}
  • 效果size: null 时,默认用 10。
方案 2:手动检查
public Page<InviteCode> findPaginatedInviteCodeByAdminIdAndSearch(Integer adminId, PageWithSearch pageWithSearch) {
    Integer page = pageWithSearch.getPage() != null ? pageWithSearch.getPage() : 0;
    Integer size = pageWithSearch.getPageSize() != null ? pageWithSearch.getPageSize() : 10;
    if (size <= 0) {
        throw new IllegalArgumentException("每页大小必须大于0");
    }
    PageRequest pageRequest = PageRequest.of(page, size);
    String field = pageWithSearch.getField();
    String value = pageWithSearch.getValue();
    if (!StringUtils.isEmpty(field) && !StringUtils.isEmpty(value)) {
        return inviteCodeRepository.findPaginatedInviteCodeByAdminIdAndFieldAndValue(adminId, field, value, pageRequest);
    } else {
        return inviteCodeRepository.findByAdminId(adminId, pageRequest);
    }
}
方案 3:校验输入

修改 BasePage

public class BasePage {
    @NotNull(message = "页码不能为空")
    Integer page = 0;
    @NotNull(message = "每页大小不能为空")
    @Min(value = 1, message = "每页大小必须大于0")
    Integer size = 10;
    // ... 其他代码 ...
}
  • 效果@Valid 在控制器层拦截 size: null,返回 400。
Mermaid 流程图:修复过程
输入: size=null
方案 1: toPageableWithDefault
size=10
PageRequest.of(0, 10)
正常返回数据
方案 3: @Valid 校验
返回 400: 'size: 每页大小不能为空'

🌈 第五幕:经验与反思

学到了啥?💡

  1. 自动拆箱的陷阱
    • Integerint 的转换遇到 null 会抛 NullPointerException,要小心!
  2. 设计与使用的脱节
    • BasePagePageWithSearch 提供了默认值工具,但没用等于白搭。
  3. 异常信息的重要性
    • NullPointerExceptiongetMessage()null,别指望它自带线索。

小建议 🌟

  • 日志救命
    log.info("page: {}, size: {}", pageWithSearch.getPage(), pageWithSearch.getPageSize());
    
  • 优先用工具方法
    • toPageable()toPageableWithDefault() 是现成的防护网,别自己造轮子。

🎬 尾声

"分页查询失败:null" 到揭开自动拆箱的秘密,这场 500 错误的破案之旅让我对 Java 类型系统又爱又恨。希望这篇博客能帮你在分页开发中少踩坑!有问题欢迎留言,咱们一起聊技术!✌️

在这里插入图片描述