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来定义结果映射规则:
- 先在
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方法 }
- 然后修改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对象:
- 修改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); }
- 在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
相关产品推荐
相关产品推荐

