
当使用 Spring Data JPA 执行原生 SQL 查询并希望将结果直接映射到非实体类(如 DTO)时,需通过 @SqlResultSetMapping 配合 @ConstructorResult 显式声明字段与构造函数参数的对应关系,否则会因类型不匹配抛出 ConverterNotFoundException。
当使用 spring data jpa 执行原生 sql 查询并希望将结果直接映射到非实体类(如 dto)时,需通过 `@sqlresultsetmapping` 配合 `@constructorresult` 显式声明字段与构造函数参数的对应关系,否则会因类型不匹配抛出 `converternotfoundexception`。
在 Spring Data JPA 中,原生查询(@Query(nativeQuery = true))默认不支持直接投影到任意 DTO 类型,尤其当该 DTO 并非 JPA 实体、也未声明为 @Entity 或 @Embeddable 时。你遇到的错误:
No converter found capable of converting from type [AbstractJpaQuery$TupleConverter$TupleBackedMap] to type [CarDto]
本质是 Spring 尝试将 JDBC 查询返回的 Map<string object></string>(即 TupleBackedMap)自动转换为 CarDto,但 Spring Data JPA 缺乏内置转换器——因为 CarDto 是一个不可变(final 字段)、无默认构造器、且未标注任何映射元数据的普通类。
✅ 正确解法:使用 @SqlResultSetMapping + @ConstructorResult
✅ 步骤一:确保 DTO 具备可反射调用的构造函数
你的 CarDto 使用了 Lombok 的 @RequiredArgsConstructor 和 private final 字段,这会生成仅含 final 参数的构造器(符合要求),但必须保证字段顺序与 SQL 查询中 SELECT 列的顺序严格一致:
@RequiredArgsConstructor
@Builder
@Getter
@Setter
public class CarDto {
private final String identification;
private final String attribute_2;
private final String attribute_3;
private final String attribute_4;
private final String attribute_5;
private final String attribute_6;
private final String childCarIdentification; // ← 对应 cr.subCarIdentification
private final String parentCarIdentification; // ← 对应 cr_1.carIdentification
}✅ 构造器签名应为:
public CarDto(String identification, String attribute_2, ..., String parentCarIdentification)
✅ 步骤二:在实体类上定义 @SqlResultSetMapping
推荐将映射定义在任一相关实体(如 CarEntity)上(必须是 @Entity 类):
@Entity(name = "Car")
@Table(name = "Car")
@SqlResultSetMapping(
name = "CarDtoMapping",
classes = @ConstructorResult(
targetClass = CarDto.class,
columns = {
@ColumnResult(name = "identification", type = String.class),
@ColumnResult(name = "attribute_2", type = String.class),
@ColumnResult(name = "attribute_3", type = String.class),
@ColumnResult(name = "attribute_4", type = String.class),
@ColumnResult(name = "attribute_5", type = String.class),
@ColumnResult(name = "attribute_6", type = String.class),
@ColumnResult(name = "childCarIdentification", type = String.class),
@ColumnResult(name = "parentCarIdentification", type = String.class)
}
)
)
@Builder
@Setter
@Getter
@AllArgsConstructor
@NoArgsConstructor
public class CarEntity {
// ... 字段保持不变
}⚠️ 注意:
-
name = "CarDtoMapping"是自定义映射名,后续查询中需引用; -
@ColumnResult.name必须与 SQL 中AS别名完全一致(大小写敏感,取决于数据库配置); - 所有列名必须显式列出,且顺序必须与
CarDto构造器参数顺序一致。
✅ 步骤三:在 Repository 中使用命名结果集映射
修改你的仓库方法,移除 nativeQuery = true,改用 JPQL 风格的原生查询绑定映射(注意:@Query 不支持 resultSetMapping,必须用 @NamedNativeQuery 或 @Query + resultClass/resultSetMapping 组合):
✅ 推荐方式:使用 @Query + resultSetMapping(Spring Data JPA 2.7+)
@Repository
public interface JpaCarRepository extends JpaRepository<CarEntity, String> {
@Query(
value = """
SELECT
c.identification, c.attribute_2, c.attribute_3, c.attribute_4,
c.attribute_5, c.attribute_6,
cr.subCarIdentification AS childCarIdentification,
cr_1.carIdentification AS parentCarIdentification
FROM Car c
LEFT JOIN CarRelation cr ON cr.carIdentification = c.identification
LEFT JOIN CarRelation cr_1 ON c.identification = cr_1.subCarIdentification
WHERE c.identification = :identification
""",
nativeQuery = true,
resultSetMapping = "CarDtoMapping" // ← 关键:指向上面定义的映射
)
Optional<CarDto> findByIdentification(@Param("identification") String identification);
}✅ 注意:
resultSetMapping属性仅在nativeQuery = true时生效,且要求映射名已通过@SqlResultSetMapping声明。
⚠️ 补充说明与避坑指南
- ❌ 不要试图用
@Query(..., nativeQuery = true)直接返回 DTO 而不配resultSetMapping—— 这是本问题的根本原因; - ❌ 避免在 DTO 中混用
@Data/@AllArgsConstructor与final字段,易导致构造器冲突;Lombok 的@RequiredArgsConstructor是安全选择; - ✅ 若 DTO 字段名与数据库列名不一致,必须用
AS别名对齐,@ColumnResult.name匹配的是别名,不是原始列名; - ✅ SQL 中
LEFT JOIN条件请再次核对:你原查询中cr_1.subComponentIdentification应为cr_1.subCarIdentification(依据CarRelationEntity字段名),否则可能查不到数据; - ? 替代方案(轻量级):若仅需简单映射,也可改用
List<object></object>+ 手动封装,但丧失类型安全与可维护性。
✅ 总结
Spring Data JPA 原生查询投影到 DTO 的标准路径是:
DTO 提供匹配顺序的构造器 → 实体类声明 @SqlResultSetMapping → Repository 方法通过 resultSetMapping 引用该映射。
这一机制绕过了 Spring 默认的 Map→Bean 转换限制,由 JPA 提供底层 ResultSet 到构造器的强类型绑定,既高效又可靠。

















