Spring Data JPA使用@Query返回DTO触发Druid SQL解析异常
问题原因与解决方案
核心问题分析
抛出com.alibaba.druid.sql.parser.ParserException的根本原因是原生SQL与JPQL语法混用,同时存在两处辅助问题:
- 开启了
nativeQuery=true(表示使用原生SQL),但查询语句却用了JPQL特有的new 全限定类名.DTO(...)构造器语法,Druid解析原生SQL时无法识别该语法,直接抛出解析异常。 - Repository泛型错误:
MzArenaRepository继承JpaRepository时指定的泛型是MzArena,但实际查询的是MzContentArena实体,上下文不匹配。 - 参数类型不匹配:方法参数是
String id,但数据库中arena_id是Integer类型,会引发后续类型转换问题。
解决方案
方案一:使用JPQL(推荐,简单直接)
修改Repository代码,去掉nativeQuery=true,改用JPQL语法操作实体类与属性:
@Repository // 修正泛型为要操作的MzContentArena实体,主键类型为Integer public interface MzContentArenaRepository extends JpaRepository<MzContentArena, Integer> { // 去掉nativeQuery=true,JPQL引用实体的属性名(arenaId而非数据库字段arena_id) @Query("select new com.qiguliuxing.dts.db.entity.MzContentArenaDTO(car.id, car.arenaId, car.contentId) from MzContentArena car where car.arenaId = ?1") // 参数类型修正为Integer,匹配数据库字段类型 List<MzContentArenaDTO> findByArenaId(Integer arenaId); }
方案二:使用原生SQL(需手动处理结果映射)
如果必须使用原生SQL,不能直接用new DTO()语法,需要通过结果映射实现:
- 先在DTO类上添加无参构造器(JPA需要实例化对象后赋值):
@Data @AllArgsConstructor public class MzContentArenaDTO implements Serializable { private Integer id; private Integer arenaId; private Integer contentId; private LocalDateTime createDate; public MzContentArenaDTO(Integer id, Integer arenaId, Integer contentId) { this.id = id; this.arenaId = arenaId; this.contentId = contentId; } // 添加无参构造器 public MzContentArenaDTO() {} }
- 修改Repository,使用原生SQL并指定结果集映射:
@Repository public interface MzContentArenaRepository extends JpaRepository<MzContentArena, Integer> { @Query(value = "select id, arena_id as arenaId, content_id as contentId from mz_content_arena where arena_id = ?1", nativeQuery = true, // 指定结果映射到DTO的属性 resultSetMapping = "MzContentArenaDTOMapping") List<MzContentArenaDTO> findByArenaId(Integer arenaId); }
- 在实体类
MzContentArena上添加结果集映射注解:
@Entity @Table(name = "mz_content_arena") // 定义结果集映射,将原生SQL的字段映射到DTO属性 @SqlResultSetMapping( name = "MzContentArenaDTOMapping", classes = @ConstructorResult( targetClass = MzContentArenaDTO.class, columns = { @ColumnResult(name = "id", type = Integer.class), @ColumnResult(name = "arenaId", type = Integer.class), @ColumnResult(name = "contentId", type = Integer.class) } ) ) public class MzContentArena { @Id private Integer id; private Integer arenaId; private Integer contentId; }
关键注意事项
- JPQL操作的是实体类和属性名,原生SQL操作的是数据库表和字段名,二者语法不能混用。
- Repository的泛型必须与要操作的实体类一致,否则JPA无法正确识别实体上下文。
- 方法参数类型要与数据库字段类型匹配,避免不必要的类型转换异常。
内容的提问来源于stack exchange,提问作者gfq
相关产品推荐
相关产品推荐

