After upgrading to Hibernate 6 and Spring Boot 3 the following throws me an error (Cannot project java.lang.Short to com.cubetrek.cubetrekplayground.database.TrackData$Sharing; Target type is not an interface and no matching Converter found
).
The @Entity class is as follows:
@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;
// loads of other fields and getters and setters...
//and here's the interface
public interface TrackMetadata {
Long getId();
@JsonSerialize(using = TitleSerializer.class, as=String.class)
String getTitle();
@Enumerated(EnumType.ORDINAL)
Sharing getSharing();
}
}
In the TrackDataRepository:
public interface TrackDataRepository extends JpaRepository<TrackData, Long>, JpaSpecificationExecutor<TrackData> {
Optional<TrackData> findById(Long id);
// and here's the query in question:
@Query(value = "SELECT trackdata.id, trackdata.title, trackdata.sharing FROM trackdata " +
"WHERE trackdata.id = :trackid", nativeQuery = true)
TrackData.TrackMetadata getMetadata(long trackid);
}
The Entity works as intented:
TrackData trackData = trackDataRepository.getReferenceById(21682L);
System.out.println(trackData.getSharing()); //works fine
but the interface is screwed up:
TrackData.TrackMetadata td2 = trackDataRepository.getMetadata(21682L);
System.out.println(td2.getTitle()); //works
System.out.println(td2.getSharing()); //ERROR!!
And the error seems to be because of the projection from short to enum:
Cannot project java.lang.Short to com.cubetrek.cubetrekplayground.database.TrackData$Sharing; Target type is not an interface and no matching Converter found
Also a custom Converter doesn't seem to do the trick:
@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];
}
}
public interface TrackMetadata {
Long getId();
@JsonSerialize(using = TitleSerializer.class, as=String.class)
String getTitle();
@Convert(converter = SharingConverter.class)
Sharing getSharing();
}
There is a workaround solution to solve this. Mark your converter class as a bean with @Component
.
@Component
@Converter(autoApply = true)
public 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];
}
}
Then use SpEL through @Value
to convert Short
to enum.
target
is an identifier that will be bound to the "aggregate root backing the projection" (Spring Data Jpa docs, section "An Open Projection").
public interface TrackMetadata {
Long getId();
String getTitle();
@Value("#{@sharingConverter.convertToEntityAttribute(target.sharing)}")
Sharing getSharing();
}