@SqlResultSetMapping实体映射引用未知列:原生SQL部分字段投影报错问题
问题原因
你遇到的这个报错,核心原因是JPA的@EntityResult会尝试映射实体类的所有持久化属性,哪怕你在原生SQL里只查询了部分字段。
你的CustomerEntity包含address、guid、audit这些持久化属性,虽然你在查询里只选了customer_name和customer_email,但Hibernate在构建CustomerEntity对象时,会试图从结果集中找到所有实体属性对应的列——结果集里没有address、customer_guid这些列,Hibernate就会抛出找不到列的错误(address2_6_0_是Hibernate内部生成的列别名,对应你的address属性)。
简单说:你要返回完整的CustomerEntity对象,但只给了部分字段的数据,Hibernate凑不出完整的实体,所以报错了。
解决方案
根据你的需求(只获取部分字段,不想加载整个实体),有两种常用的正确做法:
方案1:使用构造函数投影
在CustomerEntity中添加一个只包含你需要字段的构造函数,然后通过@SqlResultSetMapping的@ConstructorResult来映射查询结果,而不是用@EntityResult。
修改后的实体和映射代码:
@NamedNativeQuery( name = "CustomerEntity.findOnlyNameAndEmailFromCustomer", query = "select customer_name, customer_email from customer", resultSetMapping = "ConstructorMapping" ) @SqlResultSetMappings(value = { @SqlResultSetMapping( name = "ConstructorMapping", classes = { @ConstructorResult( targetClass = CustomerEntity.class, columns = { @ColumnResult(name = "customer_name", type = String.class), @ColumnResult(name = "customer_email", type = String.class) } ) } ) }) @Entity class CustomerEntity { // 新增对应投影字段的构造函数(注意:要和查询列的顺序一致) public CustomerEntity(String name, String email) { this.name = name; this.email = email; } // 必须保留无参构造函数(JPA要求) public CustomerEntity() {} // 原有的属性和getter/setter @Column(name="customer_name") private String name; @Column(name="customer_email") private String email; @Column(name="address") private String address; @Id @GeneratedValue(generator = "uuid2") @GenericGenerator(name = "uuid2", strategy = "uuid2") @Access(AccessType.PROPERTY) @Column(columnDefinition = "VARCHAR(36)", name = "customer_guid") @Type(type = "uuid-char") private UUID guid; @Embedded private AuditFields audit; }
Repository层保持不变:
@Query(nativeQuery = true) List<CustomerEntity> findOnlyNameAndEmailFromCustomer();
方案2:使用DTO投影(更推荐)
创建一个只包含你需要字段的DTO类,避免操作完整的实体类,更符合“仅投影部分字段”的需求。
- 首先创建DTO:
public class CustomerProjectionDTO { private String name; private String email; // 构造函数要和查询列的顺序一致 public CustomerProjectionDTO(String name, String email) { this.name = name; this.email = email; } // getter方法 public String getName() { return name; } public String getEmail() { return email; } }
- 修改实体的命名查询和结果映射:
@NamedNativeQuery( name = "CustomerEntity.findOnlyNameAndEmailProjection", query = "select customer_name, customer_email from customer", resultSetMapping = "DTOMapping" ) @SqlResultSetMappings(value = { @SqlResultSetMapping( name = "DTOMapping", classes = { @ConstructorResult( targetClass = CustomerProjectionDTO.class, columns = { @ColumnResult(name = "customer_name", type = String.class), @ColumnResult(name = "customer_email", type = String.class) } ) } ) }) @Entity class CustomerEntity { // 原有的属性和方法不变 }
- Repository层修改返回类型:
@Query(nativeQuery = true) List<CustomerProjectionDTO> findOnlyNameAndEmailProjection();
为什么原来的方式不行?
@EntityResult的设计目标是映射完整的实体对象,它要求结果集包含实体所有持久化属性对应的列(除非属性被标记为@Transient,或者是nullable且Hibernate允许延迟加载,但这里显然不适用)。如果你只查询部分字段,Hibernate无法构建完整的实体实例,就会抛出列缺失的错误。
内容的提问来源于stack exchange,提问作者ng.newbie

