升级Hibernate6+SpringBoot3后,原生查询接口投影枚举转换报错求助
Spring Boot 3+Hibernate 6原生查询接口投影枚举转换失败问题
报错信息
Cannot project java.lang.Short to com.cubetrek.cubetrekplayground.database.TrackData$Sharing; Target type is not an interface and no matching Converter found
相关代码
实体类TrackData
@Entity(name = "trackdata") @Table(name = "trackdata") public class TrackData implements Serializable { public enum Sharing { PRIVATE, FRIENDS, PUBLIC; } @Getter @Setter @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; @Getter @Setter @Column(name = "title") private String title; @Getter @Setter @Enumerated(EnumType.ORDINAL) @Column(name = "sharing") private Sharing sharing; // 其他字段、Getter/Setter省略... // 投影接口 public interface TrackMetadata { Long getId(); @JsonSerialize(using = TitleSerializer.class, as=String.class) String getTitle(); @Enumerated(EnumType.ORDINAL) Sharing getSharing(); } }
仓库接口TrackDataRepository
public interface TrackDataRepository extends JpaRepository<TrackData, Long>, JpaSpecificationExecutor<TrackData> { Optional<TrackData> findById(Long id); // 出问题的原生查询 @Query(value = "SELECT trackdata.id, trackdata.title, trackdata.sharing FROM trackdata " + "WHERE trackdata.id = :trackid", nativeQuery = true) TrackData.TrackMetadata getMetadata(long trackid); }
测试情况
- 直接查询实体类时,枚举字段读取正常:
TrackData trackData = trackDataRepository.getReferenceById(21682L); System.out.println(trackData.getSharing()); // 正常输出
- 使用接口投影时,
title字段正常,但sharing枚举触发报错:
TrackData.TrackMetadata td2 = trackDataRepository.getMetadata(21682L); System.out.println(td2.getTitle()); // 正常输出 System.out.println(td2.getSharing()); // 触发报错
- 尝试自定义
AttributeConverter仍无法解决:
@Converter public static class SharingConverter implements AttributeConverter<Sharing, Short> { @Override public Short convertToDatabaseColumn(Sharing sharing) { return (short)sharing.ordinal(); } @Override public Sharing convertToEntityAttribute(Short dbData) { return Sharing.values()[dbData]; } }
在投影接口中添加@Convert注解也无效:
public interface TrackMetadata { Long getId(); @JsonSerialize(using = TitleSerializer.class, as=String.class) String getTitle(); @Convert(converter = SharingConverter.class) Sharing getSharing(); }
问题原因
Hibernate 6对原生查询的接口投影处理逻辑发生了变化:
- 直接查询实体时,Hibernate会自动使用实体上的
@Enumerated注解或转换器处理枚举类型; - 原生查询的接口投影不会自动关联实体上的枚举映射规则,数据库返回的
short类型无法直接转换为枚举类,且接口投影不支持在方法上使用@Enumerated或@Convert注解触发转换。
解决方案
方案1:改用JPQL查询替代原生查询
JPQL查询会自动继承实体的映射规则,无需额外处理枚举转换:
@Query("SELECT td.id, td.title, td.sharing FROM trackdata td WHERE td.id = :trackid") TrackData.TrackMetadata getMetadata(long trackid);
注:JPQL中使用的trackdata是实体类上@Entity(name = "trackdata")配置的实体名
方案2:使用DTO类投影替代接口投影
创建DTO类,在字段上添加枚举映射注解,Hibernate会自动处理类型转换:
- 定义DTO类(必须包含与查询返回字段顺序一致的全参构造器):
public class TrackMetadataDto { private Long id; @JsonSerialize(using = TitleSerializer.class, as=String.class) private String title; @Enumerated(EnumType.ORDINAL) private TrackData.Sharing sharing; public TrackMetadataDto(Long id, String title, TrackData.Sharing sharing) { this.id = id; this.title = title; this.sharing = sharing; } // Getter方法 public Long getId() { return id; } public String getTitle() { return title; } public TrackData.Sharing getSharing() { return sharing; } }
- 修改仓库方法的返回类型:
@Query(value = "SELECT trackdata.id, trackdata.title, trackdata.sharing FROM trackdata " + "WHERE trackdata.id = :trackid", nativeQuery = true) TrackMetadataDto getMetadata(long trackid);
方案3:原生查询中手动转换枚举值
通过SQL将枚举序数值转换为字符串,再在业务代码中转换为枚举:
- 修改原生查询:
@Query(value = "SELECT trackdata.id, trackdata.title, " + "CASE trackdata.sharing " + " WHEN 0 THEN 'PRIVATE' " + " WHEN 1 THEN 'FRIENDS' " + " WHEN 2 THEN 'PUBLIC' " + "END AS sharing " + "FROM trackdata WHERE trackdata.id = :trackid", nativeQuery = true) TrackData.TrackMetadata getMetadata(long trackid);
- 修改投影接口的
getSharing方法返回类型为String:
public interface TrackMetadata { Long getId(); @JsonSerialize(using = TitleSerializer.class, as=String.class) String getTitle(); String getSharing(); }
- 业务代码中手动转换为枚举:
TrackData.TrackMetadata metadata = trackDataRepository.getMetadata(21682L); TrackData.Sharing sharing = TrackData.Sharing.valueOf(metadata.getSharing());
总结
优先选择方案1(JPQL查询),它最简洁且能自动复用实体的映射规则;如果必须使用原生查询,方案2(DTO投影)的代码侵入性更低,比方案3的手动转换更易维护。
内容的提问来源于stack exchange,提问作者Folli
相关产品推荐
相关产品推荐

