I've got a similar scenario and so far am just storing a defined ID (as opposed to the ordinal or value). I took org.hibernate.type.EnumType as a starting point and so far have (not too clean I know)
Code:
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.AssertionFailure;
import org.hibernate.HibernateException;
import org.hibernate.usertype.EnhancedUserType;
import org.hibernate.usertype.ParameterizedType;
import org.hibernate.util.ReflectHelper;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
/**
* Enum type mapper (customised from org.hibernate.type.EnumType)
* Try and find the appropriate SQL type depending on column metadata
*
* @author Emmanuel Bernard
* @author Peter Johnson
*/
//TODO implements readobject/writeobject to recalculate the enumclasses
public class EnumType implements EnhancedUserType, ParameterizedType, Serializable {
private static final Log log = LogFactory.getLog(EnumType.class);
public static final String ENUM = "enumClass";
public static final String TYPE = "type";
private static Map<Class, Map<String, Enum>> enumValues = new HashMap<Class, Map<String, Enum>>();
private Class<? extends Enum> enumClass;
private int sqlType = Types.INTEGER;
private boolean hasOwnId = false;
public int[] sqlTypes() {
return new int[]{sqlType};
}
public Class returnedClass() {
return enumClass;
}
public boolean equals(Object x, Object y) throws HibernateException {
return x == y;
}
public int hashCode(Object x) throws HibernateException {
return x == null ? 0 : x.hashCode();
}
public Object nullSafeGet(ResultSet rs, String[] names, Object owner) throws HibernateException, SQLException {
if (rs.wasNull()) {
/*log.debug("Returning null as column " + names[0]);*/
return null;
}
//log.debug("Column: " + names[0]);
if (rs.getObject(names[0]) == null) {
//log.info("Value was null");
return null;
}
//log.debug("Value: " + rs.getObject(names[0]).toString());
return fromXMLString(rs.getObject(names[0]).toString());
}
public void nullSafeSet(PreparedStatement st, Object value, int index) throws HibernateException, SQLException {
if (value == null) {
/*log.debug("nullSafeSet: Binding null to parameter: " + index);*/
st.setNull(index, sqlType);
} else {
if (isOrdinal(sqlType)) {
int ordinal = hasOwnId ? ((HibernateEnum) value).getId() : ((Enum) value).ordinal();
/*log.debug("nullSafeSet: Binding '" + ordinal + "' to parameter: " + index);*/
st.setObject(index, Integer.valueOf(ordinal), sqlType);
} else {
String enumString = ((Enum) value).name();
/*log.debug("nullSafeSet: Binding '" + enumString + "' to parameter: " + index);*/
st.setObject(index, enumString, sqlType);
}
}
}
private boolean isOrdinal(int paramType) {
switch (paramType) {
case Types.INTEGER:
case Types.NUMERIC:
case Types.SMALLINT:
case Types.TINYINT:
case Types.BIGINT:
case Types.DECIMAL: //for Oracle Driver
case Types.DOUBLE: //for Oracle Driver
case Types.FLOAT: //for Oracle Driver
return true;
case Types.CHAR:
case Types.LONGVARCHAR:
case Types.VARCHAR:
return false;
default:
throw new HibernateException("Unable to persist an Enum in a column of SQL Type: " + paramType);
}
}
public Object deepCopy(Object value) throws HibernateException {
return value;
}
public boolean isMutable() {
return false;
}
public Serializable disassemble(Object value) throws HibernateException {
return (Serializable) value;
}
public Object assemble(Serializable cached, Object owner) throws HibernateException {
return cached;
}
public Object replace(Object original, Object target, Object owner) throws HibernateException {
return original;
}
public void setParameterValues(Properties parameters) {
/*log.info("Initialising EnumType parameters for " + parameters.getProperty(ENUM));*/
String enumClassName = parameters.getProperty(ENUM);
try {
enumClass = ReflectHelper.classForName(enumClassName, this.getClass()).asSubclass(Enum.class);
for (Class iface : enumClass.getInterfaces()) {
if (iface.isAssignableFrom(HibernateEnum.class)) {
hasOwnId = true; // determines whether ordinal or Id (via getId()) should be used
break;
}
}
} catch (ClassNotFoundException exception) {
throw new HibernateException("Enum class not found", exception);
}
//this is threadsafe to do it here, setParameterValues() is called sequencially
if (!enumValues.containsKey(enumClass)) {
initEnumValue();
/*for (String key : enumValues.get(enumClass).keySet())
log.debug("Key: " + key + " : " + enumValues.get(enumClass).get(key));*/
}
String type = parameters.getProperty(TYPE);
if (type != null) {
sqlType = Integer.decode(type);
}
}
private void initEnumValue() {
/*log.info("Initialising enumValues for " + enumClass.getName());*/
try {
Method method = enumClass.getDeclaredMethod("values", new Class[0]);
Enum[] values = (Enum[]) method.invoke(null, new Object[0]);
HashMap<String, Enum> enumValuesMap = new HashMap<String, Enum>();
for (Enum value : values) {
enumValuesMap.put(value.toString(), value);
enumValuesMap.put(Integer.toString(hasOwnId ? ((HibernateEnum) value).getId() : value.ordinal()),
value);
}
enumValues.put(enumClass, enumValuesMap);
} catch (Exception e) {
throw new HibernateException("Error while accessing enum.values(): " + enumClass, e);
}
}
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
//FIXME Hum, I think I break the thread safety here
ois.defaultReadObject();
initEnumValue();
}
public String objectToSQLString(Object value) {
if (isOrdinal(sqlType)) {
if (hasOwnId) {
return ((HibernateEnum) value).getId().toString();
} else {
return Integer.toString(((Enum) value).ordinal());
}
} else {
return '\'' + ((Enum) value).name() + '\'';
}
}
public String toXMLString(Object value) {
if (isOrdinal(sqlType)) {
if (hasOwnId) {
return ((HibernateEnum) value).getId().toString();
} else {
return Integer.toString(((Enum) value).ordinal());
}
} else {
return ((Enum) value).name();
}
}
public Object fromXMLString(String xmlValue) {
/*log.info("fromXMLString: Looking for class " + enumClass + " key " + xmlValue);*/
if (!enumValues.containsKey(enumClass)) {
throw new AssertionFailure("enumValues not preprocessed: " + enumClass);
}
if (enumValues.get(enumClass).containsKey(xmlValue)) {
return enumValues.get(enumClass).get(xmlValue);
} else {
throw new IllegalArgumentException(
"fromXMLString: Unknown key value for enum " + enumClass + ": " + xmlValue);
}
}
}
Any Enums I want to have the ID persist instead of the ordinal or value implement
Code:
public interface HibernateEnum {
public Integer getId();
}
So to use
Code:
<!-- Define type here when using as a discriminator -->
<typedef name="AddressType" class="EnumType">
<param name="enumClass">AddressType</param>
<param name="type">4</param>
<!-- Defines the column type: 4 = INTEGER, 12 = VARCHAR -->
</typedef>
then as a discriminator
Code:
<discriminator column="Address_Type" type="AddressType" not-null="true"/>
and / or normal property
Code:
<property name="type" column="Address_Type" type="AddressType" not-null="true" insert="false" update="false"/>
With the Enum looking like
Code:
public enum AddressType implements HibernateEnum {
POSTAL( 1,"Postal"),
PHYSICAL( 2,"Physical");
AddressType(Integer id, String value) {
this.id = id;
this.value = value;
}
private Integer id;
private String value;
public Integer getId() {
return id;
}
public String getValue() {
return value;
}
// Alternative approach is to use a switch statement
public static AddressType fromId(Integer key) {
for (AddressType addressType : values()) {
if (addressType.getId().equals(key)) {
return addressType;
}
}
return null;
}
}
Hope this helps.
If / when I get time I was going to try to implement CompositeUserType in a hope to persist / maintain the values of the Enum in the DB but I'm not sure if that will work or if it is even the right approach to take to achieve it.