Java Enums, JPA and Postgres enums - How do I make them work together?

Viewed 32433

We have a postgres DB with postgres enums. We are starting to build JPA into our application. We also have Java enums which mirror the postgres enums. Now the big question is how to get JPA to understand Java enums on one side and postgres enums on the other? The Java side should be fairly easy but I'm not sure how to do the postgres side.

5 Answers

This involves making multiple mappings.

First, a Postgres enum is returned by the JDBC driver as an instance of type PGObject. The type property of this has the name of your postgres enum, and the value property its value. (The ordinal is not stored however, so technically it's not an enum anymore and possibly completely useless because of this)

Anyway, if you have a definition like this in Postgres:


CREATE TYPE mood AS ENUM ('sad', 'ok', 'happy');

Then the resultset will contain a PGObject with type "mood" and value "happy" for a column having this enum type and a row with the value 'happy'.

Next thing to do is writing some interceptor code that sits between the spot where JPA reads from the raw resultset and sets the value on your entity. E.g. suppose you had the following entity in Java:


public @Entity class Person {

  public static enum Mood {sad, ok, happy}

  @Id Long ID;
  Mood mood;

}

Unfortunately, JPA does not offer an easy interception point where you can do the conversion from PGObject to the Java enum Mood. Most JPA vendors however have some proprietary support for this. Hibernate for instance has the TypeDef and Type annotations for this (from Hibernate-annotations.jar).


@TypeDef(name="myEnumConverter", typeClass=MyEnumConverter.class)
public @Entity class Person {

  public static enum Mood {sad, ok, happy}

  @Id Long ID;
  @Type(type="myEnumConverter") Mood mood;

These allow you to supply an instance of UserType (from Hibernate-core.jar) that does the actual conversion:


public class MyEnumConverter implements UserType {

    private static final int[] SQL_TYPES = new int[]{Types.OTHER};

    public Object nullSafeGet(ResultSet arg0, String[] arg1, Object arg2) throws HibernateException, SQLException {

        Object pgObject = arg0.getObject(X); // X is the column containing the enum

        try {
            Method valueMethod = pgObject.getClass().getMethod("getValue");
            String value = (String)valueMethod.invoke(pgObject);            
            return Mood.valueOf(value);     
        }
        catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }

    public int[] sqlTypes() {       
        return SQL_TYPES;
    }

    // Rest of methods omitted

}

This is not a complete working solution, but just a quick pointer in hopefully the right direction.

it's work for me

@org.hibernate.annotations.TypeDef(name = "enum_type", typeClass = PostgreSQLEnumType.class)
public class SomeEntity {

...

@Enumerated(EnumType.STRING)
@Type(type = "enum_type")
private AdType name;

}

and

import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.type.EnumType;

import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Types;

public class PostgreSQLEnumType extends EnumType {

@Override
public void nullSafeSet(PreparedStatement ps, Object obj, int index,
                        SharedSessionContractImplementor session) throws HibernateException, SQLException {
    if (obj == null) {
        ps.setNull(index, Types.OTHER);
    } else {
        ps.setObject(index, obj.toString(), Types.OTHER);
    }
 }
}

I tried the above suggestions without success

The only thing I could get working was making the POSTGRES def a TEXT type, and using the @Enumerated(STRING) annotation in the entity.

Ex (In Kotlin):

CREATE TABLE IF NOT EXISTS some_example_enum_table
(
    id                      UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    some_enum               TEXT NOT NULL,
);

Example Kotlin Class:

enum class SomeEnum { HELLO, WORLD }

@Entity(name = "some_example_enum_table")
data class EnumExampleEntity(
    @Id
    @GeneratedValue
    var id: UUID? = null,

    @Enumerated(EnumType.STRING)
    var some_enum: SomeEnum = SomeEnum.HELLO,
)

Then my JPA lookups actually worked:

@Repository
interface EnumExampleRepository : JpaRepository<EnumExampleEntity, UUID> {
    fun countBySomeEnumNot(status: SomeEnum): Int
}
Related