I followed the link on Wiki page on mapping typesafe enumerations (
http://www.hibernate.org/288.html). Enumeration values seem to be properly mapped when the object is saved using getSession().saveOrUpdate(entity). However, when I use an HQL update the enum value seems to be incorrectly set. It seems that the value that gets set in the database is the serialized class rather than the enum value.
This looks very similar to the problem described in
http://forum.hibernate.org/viewtopic.php?t=949454 even though I do not use Java 5 Enums but typesafe enums.
I realize that I could work around this by setting the value in the query to my enumeration's name method instead of using the enumeration. However, I was hoping that I could use the same Hibernate mapping class for both character and integer mapping and wanted to be able to switch the way I map this enum without having to change my code (I have a class that can map an enum either to its name or value based on the parameter that gets passed to the Hibernate enum mapping class).
The base enum class looks as follows:
public abstract class AbstractEnum implements Serializable {
protected static final int DEFAULT_SIZE = 10;
protected int value;
protected transient String name;
protected AbstractEnum(String name, int value) {
this.value = value;
this.name = name;
add();
}
protected void add() {
}
public static AbstractEnum get(String name) {
return null;
}
public static AbstractEnum get(int value) {
return null;
}
public static AbstractEnum get(String name, boolean ignoreCase) {
return null;
}
public int getValue() {
return value;
}
public String getName() {
return name;
}
protected Object readResolve() throws ObjectStreamException {
return get(this.value);
}
public String toString() {
return name;
}
}
A concrete enum class is as follows:
public class EntityStatus extends AbstractEnum {
protected static Map nameMap = new HashMap(DEFAULT_SIZE);
protected static Map ignoreCaseNameMap = new HashMap(DEFAULT_SIZE);
protected static Map valueMap = new HashMap(DEFAULT_SIZE);
private static final int ACTIVE_VALUE = 1;
private static final int ACTIVATION_PENDING_VALUE = 2;
private static final int SUSPENDED_VALUE = 3;
private static final int DELETE_PENDING_VALUE = 4;
private static final int DELETED_VALUE = 5;
private static final String ACTIVE_NAME = "A";
private static final String ACTIVATION_PENDING_NAME = "P";
private static final String SUSPENDED_NAME = "S";
private static final String DELETE_PENDING_NAME = "I";
private static final String DELETED_NAME = "D";
public static EntityStatus ACTIVE = new EntityStatus(ACTIVE_NAME,
ACTIVE_VALUE);
public static EntityStatus ACTIVATION_PENDING = new EntityStatus(
ACTIVATION_PENDING_NAME, ACTIVATION_PENDING_VALUE);
public static EntityStatus SUSPENDED = new EntityStatus(SUSPENDED_NAME,
SUSPENDED_VALUE);
public static EntityStatus DELETE_PENDING = new EntityStatus(
DELETE_PENDING_NAME, DELETE_PENDING_VALUE);
public static EntityStatus DELETED = new EntityStatus(DELETED_NAME,
DELETED_VALUE);
private EntityStatus(String name, int value) {
super(name, value);
}
protected void add() {
nameMap.put(this.name, this);
valueMap.put(new Integer(this.value), this);
ignoreCaseNameMap.put(this.name.toUpperCase(), this);
}
public static EntityStatus get(String name) {
return (EntityStatus) nameMap.get(name);
}
public static EntityStatus get(int value) {
return (EntityStatus) valueMap.get(value);
}
public static EntityStatus get(String name, boolean ignoreCase) {
EntityStatus rc = null;
if (ignoreCase == true) {
rc = (EntityStatus) ignoreCaseNameMap.get(name);
} else {
rc = get(name);
}
return rc;
}
public static EntityStatus getByName(String name) {
return (EntityStatus) get(name);
}
public static boolean isActive(String strStatus) {
return ACTIVE_NAME.equals(strStatus);
}
public static EntityStatus getByValue(int value) {
return (EntityStatus) get(value);
}
// public String toString() {
// return "Name: " + getName() + ", value: " + getValue();
// }
}
The class that maps enums is shown below:
public class EnumCharHBType implements UserType, ParameterizedType {
private static final int[] SQL_TYPES = new int[] { Types.VARCHAR };
private Class targetClass;
public void setParameterValues(Properties parameters) {
String targetClassName = parameters.getProperty("targetClass");
try {
targetClass = Class.forName(targetClassName);
if (!(AbstractEnum.class.isAssignableFrom(targetClass))) {
throw new HibernateException("Class " + targetClassName
+ " is not an AbstractEnum");
}
} catch (ClassNotFoundException e) {
throw new HibernateException("Class " + targetClassName
+ " not found ", e);
}
}
public int[] sqlTypes() {
return SQL_TYPES;
}
public Class returnedClass() {
return targetClass;
}
public boolean equals(Object x, Object y) throws HibernateException {
return (x == y);
}
public int hashCode(Object x) throws HibernateException {
return x.hashCode();
}
public Object nullSafeGet(ResultSet rs, String[] names, Object owner)
throws HibernateException, SQLException {
Object result = null;
String value = rs.getString(names[0]);
if (!rs.wasNull()) {
Method getter = null;
try {
getter = targetClass.getMethod("get",
new Class[] { String.class });
if (getter != null) {
result = getter.invoke(null, new Object[] { value });
}
} catch (Exception ex) {
// Catch any error, otherwise you need several catches
throw new HibernateException("Class " + targetClass.getName()
+ " not a valid AbstractEnum", ex);
}
}
return result;
}
public void nullSafeSet(PreparedStatement st, Object value, int index)
throws HibernateException, SQLException {
if (value == null) {
st.setNull(index, Types.VARCHAR);
} else {
String enumName = ((AbstractEnum) value).getName();
st.setString(index, enumName);
}
}
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;
}
}
Mapping for the enum property:
<property name="status" >
<column name="usr_status"/>
<type name="com.mytest.dao.hibernate.EnumCharHBType">
<param name="targetClass">com.mytest.common.enums.EntityStatus</param>
</type>
</property>
The update method that fails looks as follows:
public void updateStatus(User user) throws DAOException {
final String UPDATE_USER_STATUS_QUERY = "update User user set user.status = ? where user.id = ?";
try {
Query query = getSession().createQuery(UPDATE_USER_STATUS_QUERY);
query.setParameter(0, user.getStatus());
query.setParameter(1, user.getId());
int counter = query.executeUpdate();
} catch (HibernateException e) {
avLogger.warn("Failed to update user status [status = "
+ user.getStatus() + ", Id = " + user.getId() + "]"
+ ", message: " + e.getMessage() + ", cause: "
+ (e.getCause() != null ? e.getCause().getMessage() : ""));
throw new DAOException(e);
}
}
The status value is set using the following: user.setStatus(EntityStatus.DELETE_PENDING);
A snippet of the log for the update statement:
[DEBUG] (2006-04-17 10:11:56,028) org.hibernate.SQL - update test_user set usr_status=? where id=?
[DEBUG] (2006-04-17 10:11:56,053) org.hibernate.util.SerializationHelper - Starting serialization of object [I]
[DEBUG] (2006-04-17 10:11:56,054) org.hibernate.type.SerializableType - binding '2c6d8085f3f280a4e3efedaef4f6e9e4e9e1aee3efededefeeaee5eef5edf3aec5eef4e9f4f9d3f4e1f4f5f3e1e2bce72ccc8df8828080f8f280a4e3efedaef4f6e9e4e9e1aee3efededefeeaee5eef5edf3aec1e2f3f4f2e1e3f4c5eef5ed6bb12b0742633ff3828082c98085f6e1ecf5e5cc8084eee1ede5f48092cceae1f6e1afece1eee7afd3f4f2e9eee7bbf8f080808084f48081c9' to parameter: 1
[DEBUG] (2006-04-17 10:11:56,054) org.hibernate.util.SerializationHelper - Starting serialization of object [I] [DEBUG] (2006-04-17 10:11:56,055) org.hibernate.type.LongType - binding '96' to parameter: 2
[DEBUG] (2006-04-17 10:11:56,057) org.hibernate.jdbc.AbstractBatcher - about to close PreparedStatement (open PreparedStatements: 1, globally: 1)
[DEBUG] (2006-04-17 10:11:56,057) org.hibernate.jdbc.AbstractBatcher - closing statement
[DEBUG] (2006-04-17 10:11:56,062) org.hibernate.jdbc.ConnectionManager - aggressively releasing JDBC connection
[DEBUG] (2006-04-17 10:11:56,063) org.hibernate.jdbc.ConnectionManager - closing JDBC connection [ (open PreparedStatements: 0, globally: 0) (open ResultSets: 0, globally: 0)]