Mapping enum to a table with hibernate annotation

58,368

Solution 1

Hibernate is kind of terrible at Enums. It's a strange failing of an otherwise pretty good ORM. The "easiest" way to get around it is to declare your Enum a custom hibernate type. Fortunately, Hibernate wrote an example implementation which you can crib verbatim into your app:

http://www.hibernate.org/265.html

They even include instructions on how to use it. This is the pattern I use whenever I end up with the need to persist enums.

Solution 2

I've created a similar class like the one suggested by hibernate only that is configurable and there is no need to create a new type only for this persistence.

Can be used like

@Type(type = "ro.raisercostin.hibernate.EnumUserType", parameters = @Parameter(name = "type", value = "DealType"))
DealType dealType;

I added an implementation of ParameterizedType to support the passed parameter.

public class EnumUserType implements UserType, ParameterizedType {

    private static final int[] SQL_TYPES = { Types.VARCHAR };
    private Class clazz = null;

    public EnumUserType() {
    }

    @Override
    public void setParameterValues(Properties parameters) {
        String className = (String) parameters.get("type");
        try {
            this.clazz = Class.forName(className);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException("Couldn't get the class for name [" + className + "].", e);
        }
    }

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

    public Class returnedClass() {
        return clazz;
    }

    public Object nullSafeGet(ResultSet resultSet, String[] names, Object owner) throws HibernateException,
            SQLException {
        String name = resultSet.getString(names[0]);
        Object result = null;
        if (!resultSet.wasNull()) {
            result = Enum.valueOf(clazz, name);
        }
        return result;
    }

    public void nullSafeSet(PreparedStatement preparedStatement, Object value, int index) throws HibernateException,
            SQLException {
        if (null == value) {
            preparedStatement.setNull(index, Types.VARCHAR);
        } else {
            preparedStatement.setString(index, ((Enum) value).name());
        }
    }

    public Object deepCopy(Object value) throws HibernateException {
        return value;
    }

    public boolean isMutable() {
        return false;
    }

    public Object assemble(Serializable cached, Object owner) throws HibernateException {
        return cached;
    }

    public Serializable disassemble(Object value) throws HibernateException {
        return (Serializable) value;
    }

    public Object replace(Object original, Object target, Object owner) throws HibernateException {
        return original;
    }

    public int hashCode(Object x) throws HibernateException {
        return x.hashCode();
    }

    public boolean equals(Object x, Object y) throws HibernateException {
        if (x == y) {
            return true;
        }
        if ((null == x) || (null == y)) {
            return false;
        }
        return x.equals(y);
    }
}

Solution 3

You could annotate the enum with @Entity and use a custoumn tuplizer to create the instances of the enum with Enum.valueOf

The enum declaration then looks like:

@Entity
@Table(name = "node_interface_type")
@Tuplizer(impl = EnumTuplizer.class)
public enum Type {
    WIRED, WIRELESS, WIRELESS_SENSOR_NODE;
    @Id
    public String name = toString();
}

And the Tuplizer is:

public class EnumTuplizer extends PojoEntityTuplizer {
    public EnumTuplizer(EntityMetamodel entityMetamodel, PersistentClass mappedEntity) {
        super(entityMetamodel, mappedEntity);
    }

    @Override
    protected Instantiator buildInstantiator(final PersistentClass persistentClass) {
        return new Instantiator() {
            @Override
            public Object instantiate(Serializable id) {
                try {
                    return Enum.valueOf(
                            (Class) persistentClass.getClass().getClassLoader().loadClass(persistentClass.getClassName()),
                            (String) id
                    );
                } catch (ClassNotFoundException e) {
                    throw new AssertionError(e);
                }
            }

            @Override
            public Object instantiate() {
                throw new UnsupportedOperationException();
            }

            @Override
            public boolean isInstance(Object object) {
                throw new UnsupportedOperationException();
            }
        };
    }
}

Solution 4

If you want to use the entity just read-only, then you can use @Formula and @Enumerated. Try something like:

@Entity
public class Deal {
   @Formula("(select text from DEAL_TYPE dt where dt.id = typeId)")
   @Enumerated(EnumType.STRING)
   DealType type;
}

Solution 5

Although far far from ideal, my solution to this problem was to use EnumStringType and a denormalized updatable view.

Share:
58,368
Thierry Roy
Author by

Thierry Roy

Android Programmer @ La Presse SOreadytohelp

Updated on July 18, 2020

Comments

  • Thierry Roy
    Thierry Roy almost 4 years

    I have a table DEAL and a table DEAL_TYPE. I would like to map this code:

    public class Deal {
       DealType type;
    }
    
    public enum DealType {
       BASE("Base"), EXTRA("Extra");
    }
    

    The problem is that the data already exist in the database. And I'm having a hard time mapping the classes to the database.

    The database looks something like that:

       TABLE DEAL {
          Long id;
          Long typeId;
       }
    
       TABLE DEAL_TYPE {
           Long id;
           String text;
       }
    

    I know I could use a simple @OneToMany relationship from deal to deal type, but I would prefer to use an enum. Is this possible?

    I almost got it working by using a EnumType.ORDINAL type. But unfortunately, my IDs in my deal type table are not sequential, and do not start at 1.

    Any suggestions?

  • Peter Becker
    Peter Becker over 12 years
    It seems Hibernate expanded the signature on two methods, but otherwise this works fine for me. Ever considered contributing it to the Hibernate project?
  • Gepsens
    Gepsens over 12 years
    I think you can now get rid of the Tuplizer with @Enumerated
  • Naor
    Naor about 12 years
    But where do you define the id column and the text column of the enum?
  • xmedeko
    xmedeko almost 12 years
    Not a bad solution, since you have considered insert and update, too. Some other solutions here are probably just for reading. Although it is possible only for few DB's, like Oracle.
  • Jeff Mc
    Jeff Mc over 11 years
    While I implemented this on MSSQL where updatable views are extremely simple, it should be possible to emulate on most RDBMS systems by placing an 'instead of' trigger on the view to modify the underlying tables.
  • yousafsajjad
    yousafsajjad over 10 years
    @raisercostin, I am trying what you have explained above. I am not sure what would be the parameter values in the annotation. Can you please elaborate on it or point to the source where I can get more information on it.
  • yousafsajjad
    yousafsajjad over 10 years
    Now I understand. The parameters are like type: DealType where type is the key used EnumUserType and value is the class (use complete class name including package).
  • t0tec
    t0tec over 9 years
    I don't understand how you map the Enum to a separate table with id and description. Could you give a code example how to to this in Hibernate annotations? It's what @Naor has asked.
  • Andrii Plotnikov
    Andrii Plotnikov almost 8 years
    you're life saver. With this I can instatiate hibernate objects as enums with custom values and use them in repositories
  • Erwan Leroux
    Erwan Leroux about 4 years