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

调用存储过程返回多行数据触发NonUniqueResultException的解决方法

问题分析与解决方案

问题描述

我有一个包含多张表的数据库,通过复杂SQL查询汇总各表数据并排序后导出为CSV文件。使用HeidiSQL创建了存储过程FORMAT_REPORT_DATA,为避免新建实体,复用存储按钮点击日志的LogReport表的Repository来调用该存储过程。手动执行存储过程可正常返回155行结果,但通过Java按钮调用时抛出错误:

Caused by: javax.persistence.NonUniqueResultException: query did not return a unique result: 155

相关代码实现

LogReportRepository

@Repository
public interface LogReportRepository extends JpaRepository<LogReport, Long>, Serializable {
    @Query(value = "CALL FORMAT_REPORT_DATA(:vonMonatParam,:bisMonatParam,:aggregationThresholdParam,:teamParam);", nativeQuery = true)
    ResultSet formatReportData(@Param("vonMonatParam") LocalDateTime fromMonth,
                               @Param("bisMonatParam") LocalDateTime untilMonth,
                               @Param("aggregationThresholdParam") int aggregationThreshold,
                               @Param("teamParam") int team);
}

LogReportImpl(Service实现)

@Service
public class LogReportImpl implements LogReportService {
    @Autowired
    LogReportRepository logReportRepository;
    @Override
    public ResultSet formatReportData(LocalDateTime fromMonth, LocalDateTime untilMonth, int aggregationThreshold, int team) {
        return logReportRepository.formatReportData(fromMonth,untilMonth,aggregationThreshold,team);
    }
}

LogReportService接口

public interface LogReportService {
    ResultSet formatReportData(LocalDateTime fromMonth, LocalDateTime untilMonth, int aggregationThreshold, int team);
}

FormatButton

public class FormatButton extends Button {
    @SpringBean
    public LogReportService logReportService;
    public FormatButton(String id, IModel<String> model) {
        super(id, model);
    }
    @Override
    public void onSubmit() {
        LocalDateTime fromMonth = LocalDateTime.of(2022,6,30,23,59,0);
        LocalDateTime untilMonth = LocalDateTime.of(2022,8,1,0,0,0);
        int aggregationThreshold = 37;
        int team = 1;
        ResultSet resultSet = logReportService.formatReportData(fromMonth,untilMonth,aggregationThreshold,team);
    }
}

错误原因

  1. JPA结果映射冲突:Spring Data JPA的@Query默认认为返回ResultSet类型的查询应该返回唯一结果,但存储过程实际返回了155行数据,触发了NonUniqueResultException。
  2. 不规范的返回类型:ResultSet是JDBC底层对象,Spring Data JPA并不支持直接返回该类型,无法正确管理其资源,还会导致结果映射逻辑混乱。

修复方案

方案1:定义DTO接收多行结果(推荐)

通过创建DTO类匹配存储过程返回的字段,让JPA将多行结果映射为DTO列表:

  1. 创建ReportDataDTO类
public class ReportDataDTO {
    // 根据存储过程返回的实际字段定义属性,示例如下
    private String teamName;
    private Integer dataCount;
    private LocalDateTime recordTime;

    // 生成全参构造方法、getter和setter
    public ReportDataDTO(String teamName, Integer dataCount, LocalDateTime recordTime) {
        this.teamName = teamName;
        this.dataCount = dataCount;
        this.recordTime = recordTime;
    }

    // getter和setter省略,自行生成
}
  1. 修改Repository方法
    调整返回类型为List<ReportDataDTO>,并修改查询语句(MySQL需用SELECT * FROM调用存储过程,确保JPA能映射到DTO):
@Repository
public interface LogReportRepository extends JpaRepository<LogReport, Long> {
    @Query(value = "SELECT * FROM FORMAT_REPORT_DATA(:vonMonatParam,:bisMonatParam,:aggregationThresholdParam,:teamParam);", nativeQuery = true)
    List<ReportDataDTO> formatReportData(@Param("vonMonatParam") LocalDateTime fromMonth,
                                         @Param("bisMonatParam") LocalDateTime untilMonth,
                                         @Param("aggregationThresholdParam") int aggregationThreshold,
                                         @Param("teamParam") int team);
}
  1. 同步修改Service层
public interface LogReportService {
    List<ReportDataDTO> formatReportData(LocalDateTime fromMonth, LocalDateTime untilMonth, int aggregationThreshold, int team);
}

@Service
public class LogReportImpl implements LogReportService {
    @Autowired
    LogReportRepository logReportRepository;

    @Override
    public List<ReportDataDTO> formatReportData(LocalDateTime fromMonth, LocalDateTime untilMonth, int aggregationThreshold, int team) {
        return logReportRepository.formatReportData(fromMonth, untilMonth, aggregationThreshold, team);
    }
}
  1. 调整Button调用逻辑
public class FormatButton extends Button {
    @SpringBean
    public LogReportService logReportService;

    public FormatButton(String id, IModel<String> model) {
        super(id, model);
    }

    @Override
    public void onSubmit() {
        LocalDateTime fromMonth = LocalDateTime.of(2022,6,30,23,59,0);
        LocalDateTime untilMonth = LocalDateTime.of(2022,8,1,0,0,0);
        int aggregationThreshold = 37;
        int team = 1;
        List<ReportDataDTO> reportDataList = logReportService.formatReportData(fromMonth,untilMonth,aggregationThreshold,team);
        // 后续处理CSV导出逻辑
    }
}

方案2:使用JdbcTemplate直接调用(无需DTO)

如果不想创建DTO,可直接用JdbcTemplate调用存储过程,返回键值对列表:

  1. 修改Service实现
@Service
public class LogReportImpl implements LogReportService {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    public List<Map<String, Object>> formatReportData(LocalDateTime fromMonth, LocalDateTime untilMonth, int aggregationThreshold, int team) {
        return jdbcTemplate.queryForList("CALL FORMAT_REPORT_DATA(?,?,?,?)",
                fromMonth, untilMonth, aggregationThreshold, team);
    }
}
  1. 同步修改Service接口
public interface LogReportService {
    List<Map<String, Object>> formatReportData(LocalDateTime fromMonth, LocalDateTime untilMonth, int aggregationThreshold, int team);
}
  1. 调整Button层接收结果
public class FormatButton extends Button {
    @SpringBean
    public LogReportService logReportService;

    public FormatButton(String id, IModel<String> model) {
        super(id, model);
    }

    @Override
    public void onSubmit() {
        LocalDateTime fromMonth = LocalDateTime.of(2022,6,30,23,59,0);
        LocalDateTime untilMonth = LocalDateTime.of(2022,8,1,0,0,0);
        int aggregationThreshold = 37;
        int team = 1;
        List<Map<String, Object>> reportDataMapList = logReportService.formatReportData(fromMonth,untilMonth,aggregationThreshold,team);
        // 后续处理CSV导出逻辑
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.25 06:16:19