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

Spring Boot JPA原生查询分页遇ConverterNotFoundException求助

问题分析与解决方案

这个问题的核心原因是:Spring Data JPA在处理原生SQL查询的分页结果时,无法自动将查询返回的标量字段(DATEE和song_change)映射到你的SongChangeCount类上——哪怕你写了构造函数,JPA也不会自动识别这种映射关系,尤其是当这个类本质上只是一个DTO(虽然你加了@Entity注解,但它并没有对应的数据表,不属于JPA管理的实体)。

下面给你几种可行的解决方案,按推荐程度排序:

方案一:在@Query中使用构造函数表达式(最简便)

修改Repository中的@Query注解,直接在查询语句前加上NEW 全类名(...)的构造函数调用,让JPA明确知道如何将查询结果映射到你的DTO类:

public interface ContentSongsRepository extends JpaRepository<ContentSongs,Long>{
    @Query(value= "SELECT NEW com.crbt.api.services.bean.SongChangeCount(date(start_date), sum(case when subscription_from in ('IVR','SMS') and remarks like '%Change Song%' then 1 else 0 end)) from subscription group by date(start_date)",
           countQuery = "SELECT count(*) from (SELECT date(start_date) from subscription group by date(start_date)) as sub",
           nativeQuery=false) // 注意这里要改为false,因为使用了JPQL的构造函数语法
    Page<SongChangeCount> getSongChangeCount2(Pageable page);
}

注意事项:

  • 必须使用全类名(包名+类名)来调用构造函数
  • 分页需要单独指定countQuery,因为分组查询的总数不能直接用默认的count逻辑生成
  • 你的SongChangeCount类构造函数参数要和查询返回的字段顺序、类型完全匹配:建议把song_change的类型从String改成Integer(因为sum函数返回的是数值类型),避免不必要的类型转换问题

调整后的SongChangeCount类:

import java.io.Serializable;
import java.util.Date;

public class SongChangeCount implements Serializable{
    private static final long serialVersionUID = -5410845856201124932L;
    private Date DATEE;
    private Integer song_change; // 改为Integer更符合计数逻辑

    public SongChangeCount(Date dATEE, Integer song_change) {
        DATEE = dATEE;
        this.song_change = song_change;
    }
    // getter和setter方法
}

方案二:使用@SqlResultSetMapping映射原生查询结果

如果你坚持要用原生SQL查询,可以通过@SqlResultSetMapping和@ConstructorResult来定义结果映射规则:

  1. 先在SongChangeCount类上添加映射注解:
import java.io.Serializable;
import java.util.Date;
import javax.persistence.SqlResultSetMapping;
import javax.persistence.ConstructorResult;
import javax.persistence.ColumnResult;

@SqlResultSetMapping(
    name = "SongChangeCountMapping",
    classes = @ConstructorResult(
        targetClass = SongChangeCount.class,
        columns = {
            @ColumnResult(name = "DATEE", type = Date.class),
            @ColumnResult(name = "song_change", type = Integer.class)
        }
    )
)
public class SongChangeCount implements Serializable{
    private static final long serialVersionUID = -5410845856201124932L;
    private Date DATEE;
    private Integer song_change;

    public SongChangeCount(Date dATEE, Integer song_change) {
        DATEE = dATEE;
        this.song_change = song_change;
    }
    // getter和setter方法
}
  1. 然后修改Repository的@Query,指定使用这个映射:
public interface ContentSongsRepository extends JpaRepository<ContentSongs,Long>{
    @Query(value= "SELECT date(start_date) as DATEE, sum(case when subscription_from in ('IVR','SMS') and remarks like '%Change Song%' then 1 else 0 end) as song_change from subscription group by date(start_date) \n#pageable\n",
           countQuery = "SELECT count(*) from (SELECT date(start_date) from subscription group by date(start_date)) as sub",
           nativeQuery=true)
    @SqlResultSetMapping(name = "SongChangeCountMapping")
    Page<SongChangeCount> getSongChangeCount2(Pageable page);
}

方案三:手动转换Object[]结果(最繁琐但灵活)

如果上面两种方法都不适用,你可以让Repository返回Page<Object[]>,然后在Service层手动把数组转换成SongChangeCount对象:

  1. 修改Repository:
public interface ContentSongsRepository extends JpaRepository<ContentSongs,Long>{
    @Query(value= "SELECT date(start_date) as DATEE, sum(case when subscription_from in ('IVR','SMS') and remarks like '%Change Song%' then 1 else 0 end) as song_change from subscription group by date(start_date) \n#pageable\n",
           countQuery = "SELECT count(*) from (SELECT date(start_date) from subscription group by date(start_date)) as sub",
           nativeQuery=true)
    Page<Object[]> getSongChangeCount2(Pageable page);
}
  1. 在Service层转换:
import java.util.stream.Collectors;

@Override
public SongChangeCountView getSongChangeCount(Pageable page) {
    Page<Object[]> songChangePageList = contantSongRepository.getSongChangeCount2(page);
    List<SongChangeCount> list = songChangePageList.getContent().stream()
        .map(objArr -> new SongChangeCount((Date) objArr[0], ((Number) objArr[1]).intValue()))
        .collect(Collectors.toList());
    Integer pageCount = songChangePageList.getTotalPages();
    Long totalElement = songChangePageList.getTotalElements();
    SongChangeCountView sccv = new SongChangeCountView();
    sccv.setLsSongChangeCountView(list);
    sccv.setPageCount(pageCount);
    sccv.setTotalElement(totalElement);
    return sccv;
}

补充说明:为什么之前更换Date类型没用?
因为根本问题不是Date类型的转换,而是JPA不知道如何把整个查询结果行映射到你的自定义类上,更换Date类型只是解决了字段级的类型问题,但核心的对象映射逻辑没解决。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 07:24:05