I face an issue with Hibernate 6 (migration to Spring Boot 3). I have a main entity class BaseEntity
with a status
attribute of class CustomStatus
. In hibernate/JPA query, I like to have this syntax select * from BaseEntity where status = 'ARCHIVED'
or select * from BaseEntity where status NOT IN ('ARCHIVED', 'CANCELLED')
. That works well with native query, but I like to use JPA to be able to call service.
Compile is fine, but at runtime, I get an error on query execution:
Can't compare test expression of type [BasicSqmPathSource(status : CustomStatus)] with element of type [basicType@4(java.lang.String,12)]
.
Initially (Hibernate 5 / Spring Boot 2.7), @Type
referencing a AbstractSingleColumnStandardBasicType
/ AbstractTypeDescriptor
was doing job perfectly.
BaseEntity
@Table(name="base_entity")
public class BaseEntity {
@Id
private Integer id;
@JavaType(CustomStatusJavaType.class)
@JdbcTypeCode(SqlTypes.VARCHAR)
private CustomStatus status;
}
CustomStatus
public class CustomStatus implements Serializable {
private final String name;
public CustomStatus (final String name) { this.name = name;}
}
CustomStatusJavaType
public class CustomStatusJavaType extends AbstractClassJavaType<CustomStatus> {
public static final CustomStatusJavaType INSTANCE = new CustomStatusJavaType ();
public CustomStatusJavaType () {
super(CustomStatus.class);
}
@Override
public String toString(CustomStatus value) {
if (value == null) return null;
return value.getName();
}
@Override
public CustomStatus fromString(CharSequence str) {
if (str == null || str.length() == 0) return null;
return new CustomStatus(str.toString());
}
@Override
public <X> X unwrap(CustomStatus value, Class<X> type, WrapperOptions options) {
if (value == null) return null;
return StringJavaType.INSTANCE.unwrap(
value == null ? null : value.getName(),
type,
options
);
}
@Override
public <X> CustomStatus wrap(X value, WrapperOptions options) {
if (value == null) return null;
return new CustomStatus (StringJavaType.INSTANCE.wrap( value, options));
}
}
UPDATE with AttributeConverter
CustomStatus
@Converter
public class CustomStatusAttributeConverter implements AttributeConverter<CustomStatus, String> {
@Override
public String convertToDatabaseColumn(CustomStatus status) {
return (null == status? null : status.getName());
}
@Override
public CustomStatus convertToEntityAttribute(String s) {
return (s == null || s.isEmpty() ? null : new CustomStatus(s));
}
}
public class BaseEntity {
@Id
private Integer id;
@Convert(converter = CustomStatusAttributeConverter.class)
private CustomStatus status;
}
Alas I get same error...It works if i write a native query, but not with JPA query.
I think the problem is your query, select * from BaseEntity where status = 'ARCHIVED'
/select * from BaseEntity where status NOT IN ('ARCHIVED', 'CANCELLED')
.
To be more exact, hibernate is not expecting String
s but CustomStatus
in your query.
If you don't want to use a native query, you could try using placeholders in your query:
@Query("select * from BaseEntity where status = :status")
List<BaseEntity> getBaseEntitiesWithStatus(CustomStatus status);
default List<BaseEntity> getArchivedBaseEntities(){
return getBaseEntitiesWithStatus(new CustomStatus("ARCHIVED"));
}
@Query("select * from BaseEntity where status NOT IN :disallowed")
List<BaseEntity> getBaseEntitiesWhereStatusNotIn(List<CustomStatus> disallowed);
default List<BaseEntity> getActiveBaseEntities(){
return getBaseEntitiesWhereStatusNotIn(List.of(
new CustomStatus("ARCHIVED"),
new CustomStatus("CANCELLED")
));
}
You could also try representing your CustomStatus
differently, e.g. as it's own entity in a 1:N
relation or by embedding it:
public class BaseEntity {
@Id
private Integer id;
@Embedded
private CustomStatus status;
//methods
}
@Embeddable
public class CustomStatus implements Serializable {
private final String name;
public CustomStatus (final String name) { this.name = name;}
//methods
}
With this, you could probably make a query like select * from BaseEntity where status.name = 'ARCHIVED'
However, that might require making name
non-final
.