This thread relates to Jira 231:
http://opensource2.atlassian.com/projects/hibernate/browse/ANN-231
Yes, we need a good way to map java 5 Enums to a lookup table in the database, using whatever integer primary keys that may exist.
I do have something working now that solves this problem in my application. The ugliness and burden of knowing the values of the primary keys fell on my Enum classes. (It was worse than I'd hoped because I cannot create an abstract BaseEnum object. Perhaps in Java 6 I will be able to do that.)
First I created an interface that all database backed enums need to implement.
Code:
public interface PersistableEnum {
int getDatabaseId();
PersistableEnum getEnumFromDatabaseId(int databaseId);
}
The second method really could be static, but then I couldn't add it to my interface.
The Enum looks like:
Code:
public enum AgentStatus implements PersistableEnum, Selectable {
PENDING_SIGNATURE(-2), // Pending agent signature. The default status of all agents.
PENDING(-1), // Pending activation.
ENABLED(1), // TThe normal status of an agent that recieves commissions.
DISABLED(2), // The status of an agent with no new customer aquisitions for 3 months. No commission is paid.
DELETED(3), // The status of a deleted agent. A deleted agent's customers are transferred to the IN-HOUSE agent.
REJECTED(4); // The status of a rejected or black-listed agent.
private int databaseId;
AgentStatus(int databaseId) {
this.databaseId = databaseId;
}
public int getDatabaseId() {
return databaseId;
}
public PersistableEnum getEnumFromDatabaseId(int databaseId) {
for (PersistableEnum p : AgentStatus.values()) {
if (p.getDatabaseId() == databaseId) {
return p;
}
}
throw new IllegalArgumentException("Database ID(" + databaseId + ")");
}
}
}
My Agent model object starts with:
Code:
@TypeDefs({
@TypeDef(
name = "agentType",
typeClass = EnumUserType.class,
parameters = {@Parameter(name = "enumClass", value = "com.viz.web.model.AgentType")}
),
@TypeDef(
name = "agentStatus",
typeClass = EnumUserType.class,
parameters = {@Parameter(name = "enumClass", value = "com.viz.web.model.AgentStatus")}
)
})
@Entity
@Table(name = "agent")
@SequenceGenerator(
name = "agent_seq",
sequenceName = "agent_seq"
)
And defines the property as:
Code:
@NotNull
@Type(type = "agentStatus")
@Column(name = "status_id")
public AgentStatus getStatus() {
return status;
}
public void setStatus(AgentStatus status) {
this.status = status;
}
EnumUserType is: (Just threw this together. Probably needs some cleanup)
Code:
public class EnumUserType extends EnumType {
private static Log log = LogFactory.getLog(EnumUserType.class);
private static Map<Class,Object[]> enumValues = new HashMap<Class, Object[]>();
private int sqlType = Types.INTEGER; //before any guessing
@Override
public Object nullSafeGet(ResultSet rs, String[] names, Object owner) throws HibernateException, SQLException {
Class<? extends PersistableEnum> enumClass = super.returnedClass();
Object object = rs.getObject(names[0]);
if (rs.wasNull()) {
log.debug("Returning null as column " + names[0]);
return null;
}
if (object instanceof Number) {
Object[] values = enumValues.get(enumClass);
if (values == null) {
try {
Method method = null;
method = enumClass.getDeclaredMethod("values", new Class[0]);
values = (Object[]) method.invoke(null, new Object[0]);
enumValues.put(enumClass, values);
}
catch (Exception e) {
throw new HibernateException("Error while accessing enum.values(): " + enumClass, e);
}
}
int databaseId = ((Number) object).intValue();
PersistableEnum p = (PersistableEnum) values[0];
PersistableEnum myEnum = p.getEnumFromDatabaseId(databaseId);
log.debug("Returning '" + databaseId + "' as column " + names[0]);
return myEnum;
} else {
throw new IllegalArgumentException("Data type returned is not a number. (EnumUserType");
}
}
@Override
public void nullSafeSet(PreparedStatement st, Object value, int index) throws HibernateException, SQLException {
//if (!guessed) guessType( st, index );
if (value == null) {
log.debug("Binding null to parameter: " + index);
st.setNull(index, sqlType);
} else {
//boolean isOrdinal = isOrdinal(sqlType);
boolean isOrdinal = true; // We're always using a numeric mapping.
if (isOrdinal) {
//int ordinal = ((Enum) value).ordinal();
int ordinal = ((PersistableEnum) value).getDatabaseId();
log.debug("Binding '" + ordinal + "' to parameter: " + index);
st.setObject(index, new Integer(ordinal), sqlType);
} else {
String enumString = ((Enum) value).name();
log.debug("Binding '" + enumString + "' to parameter: " + index);
st.setObject(index, enumString, sqlType);
}
}
}
}
So, what I have works great. Alas, it is not pretty, and violates the idea of having a seperation of concerns.
My hope is someone will know how to achieve the same effect with a more elegant solution. Perhaps a seperate mapping of db values to enum values, stored in properties or an xml file?