You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

升级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对原生查询的接口投影处理逻辑发生了变化:

  1. 直接查询实体时,Hibernate会自动使用实体上的@Enumerated注解或转换器处理枚举类型;
  2. 原生查询的接口投影不会自动关联实体上的枚举映射规则,数据库返回的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会自动处理类型转换:

  1. 定义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; }
}
  1. 修改仓库方法的返回类型:
@Query(value = "SELECT trackdata.id, trackdata.title, trackdata.sharing FROM trackdata " +
        "WHERE trackdata.id = :trackid", nativeQuery = true)
TrackMetadataDto getMetadata(long trackid);

方案3:原生查询中手动转换枚举值

通过SQL将枚举序数值转换为字符串,再在业务代码中转换为枚举:

  1. 修改原生查询:
@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);
  1. 修改投影接口的getSharing方法返回类型为String:
public interface TrackMetadata {
    Long getId();
    @JsonSerialize(using = TitleSerializer.class, as=String.class)
    String getTitle();
    String getSharing();
}
  1. 业务代码中手动转换为枚举:
TrackData.TrackMetadata metadata = trackDataRepository.getMetadata(21682L);
TrackData.Sharing sharing = TrackData.Sharing.valueOf(metadata.getSharing());

总结

优先选择方案1(JPQL查询),它最简洁且能自动复用实体的映射规则;如果必须使用原生查询,方案2(DTO投影)的代码侵入性更低,比方案3的手动转换更易维护。

内容的提问来源于stack exchange,提问作者Folli

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 09:05:56