Hibernate mapping between PostgreSQL enum and Java enum
HQL
Aliasing correctly and using the qualified property name was the first part of the solution.
<query name="getAllMoves">
<![CDATA[
from Move as move
where move.directionToMove = :direction
]]>
</query>
Hibernate mapping
@Enumerated(EnumType.STRING)
still didn't work, so a custom UserType
was necessary. The key was to correctly override nullSafeSet
like in this answer https://stackoverflow.com/a/7614642/1090474 and similar implementations from the web.
@Override
public void nullSafeSet(PreparedStatement st, Object value, int index, SessionImplementor session) throws HibernateException, SQLException {
if (value == null) {
st.setNull(index, Types.VARCHAR);
}
else {
st.setObject(index, ((Enum) value).name(), Types.OTHER);
}
}
Detour
implements ParameterizedType
wasn't cooperating:
org.hibernate.MappingException: type is not parameterized: full.path.to.PGEnumUserType
so I wasn't able to annotate the enum property like this:
@Type(type = "full.path.to.PGEnumUserType",
parameters = {
@Parameter(name = "enumClass", value = "full.path.to.Move$Direction")
}
)
Instead, I declared the class like so:
public class PGEnumUserType<E extends Enum<E>> implements UserType
with a constructor:
public PGEnumUserType(Class<E> enumClass) {
this.enumClass = enumClass;
}
which, unfortunately, means any other enum property similarly mapped will need a class like this:
public class HibernateDirectionUserType extends PGEnumUserType<Direction> {
public HibernateDirectionUserType() {
super(Direction.class);
}
}
Annotation
Annotate the property and you're done.
@Column(name = "directiontomove", nullable = false)
@Type(type = "full.path.to.HibernateDirectionUserType")
private Direction directionToMove;
Other notes
EnhancedUserType
and the three methods it wants implementedpublic String objectToSQLString(Object value) public String toXMLString(Object value) public String objectToSQLString(Object value)
didn't make any difference I could see, so I stuck with
implements UserType
.- Depending on how you're using the class, it might not be strictly necessary to make it postgres-specific by overriding
nullSafeGet
in the way the two linked solutions did. - If you're willing to give up the postgres enum, you can make the column
text
and the original code will work without extra work.
You can simply get these types via Maven Central using the Hibernate Types dependency:
<dependency>
<groupId>com.vladmihalcea</groupId>
<artifactId>hibernate-types-52</artifactId>
<version>${hibernate-types.version}</version>
</dependency>
If you easily map Java Enum to a PostgreSQL Enum column type using the following custom Type:
public class PostgreSQLEnumType extends org.hibernate.type.EnumType {
public void nullSafeSet(
PreparedStatement st,
Object value,
int index,
SharedSessionContractImplementor session)
throws HibernateException, SQLException {
if(value == null) {
st.setNull( index, Types.OTHER );
}
else {
st.setObject(
index,
value.toString(),
Types.OTHER
);
}
}
}
To use it, you need to annotate the field with the Hibernate @Type
annotation as illustrated in the following example:
@Entity(name = "Post")
@Table(name = "post")
@TypeDef(
name = "pgsql_enum",
typeClass = PostgreSQLEnumType.class
)
public static class Post {
@Id
private Long id;
private String title;
@Enumerated(EnumType.STRING)
@Column(columnDefinition = "post_status_info")
@Type( type = "pgsql_enum" )
private PostStatus status;
//Getters and setters omitted for brevity
}
This mapping assumes you have the post_status_info
enum type in PostgreSQL:
CREATE TYPE post_status_info AS ENUM (
'PENDING',
'APPROVED',
'SPAM'
)
That's it, it works like a charm. Here's a test on GitHub that proves it.