Here is a mechanism that lets you decouple the enum value names and ordinals from the data, while still making it easy to map enum fields to database columns.
The solution can be specific to an enum type, but it would be better to have all your persistent enum types implement the same interface to reuse the mechanism. Let's define the PersistentEnum interface:
public interface PersistentEnum { int getId(); }
and it will be implemented by enum types, for example:
public enum Gender implements PersistentEnum { MALE(0), FEMALE(1); private final int id; Gender(int id) { this.id = id } @Override public int getId() { return id; } }
Now we need to define the Hibernate User Type that will do the conversion both ways. Each enum requires its own user type, so first we will code an abstract superclass that works with the PersistentEnum interface and then subclass it for each enum:
public abstract class PersistentEnumUserType<T extends PersistentEnum> implements UserType { @Override public Object assemble(Serializable cached, Object owner) throws HibernateException { return cached; } @Override public Object deepCopy(Object value) throws HibernateException { return value; } @Override public Serializable disassemble(Object value) throws HibernateException { return (Serializable)value; } @Override public boolean equals(Object x, Object y) throws HibernateException { return x == y; } @Override public int hashCode(Object x) throws HibernateException { return x == null ? 0 : x.hashCode(); } @Override public boolean isMutable() { return false; } @Override public Object nullSafeGet(ResultSet rs, String[] names, Object owner) throws HibernateException, SQLException { int id = rs.getInt(names[0]); if(rs.wasNull()) { return null; } for(PersistentEnum value : returnedClass().getEnumConstants()) { if(id == value.getId()) { return value; } } throw new IllegalStateException("Unknown " + returnedClass().getSimpleName() + " id"); } @Override public void nullSafeSet(PreparedStatement st, Object value, int index) throws HibernateException, SQLException { if (value == null) { st.setNull(index, Types.INTEGER); } else { st.setInt(index, ((PersistentEnum)value).getId()); } } @Override public Object replace(Object original, Object target, Object owner) throws HibernateException { return original; } @Override public abstract Class<T> returnedClass(); @Override public int[] sqlTypes() { return new int[]{Types.INTEGER}; } }
The interesting methods are nullSafeGet() which is called when the resultset from the database is mapped to an object, and nullSafeSet() which is called when the fields of an object are mapped to SQL parameters of insert/update/delete statements.
The extension point is the abstract method returnedClass() - in every subclass we will override it so it returns the specific enum class. A User Type for the Gender enum defined above would like this:
public class GenderUserType extends PersistentEnumUserType<Gender> { @Override public Class<Gender> returnedClass() { return Gender.class; } }
The last thing to do is to configure fields of enum types to use the appropriate user types:
@Entity public class Person { @Type(type="it.recompile.GenderUserType") private Gender gender; // other fields and methods... }
Note that the type attribute should contain the fully qualified class of the user type, including the package.
That's it - now you can safely make changes to your enum classes without worrying about problems with existing data, as long as you don't change the id values in the constructors (which you have no reason to do).