Member Menu
 
 Monthly JBoss newsletter:
 
Java Persistence with Hibernate
CaveatEmptor

Template for mapping a jdk 1.5 enum with a SMALLINT column (Hib 3.0.x)

Benoit Xhenseval, http://www.Appendium.com

Sometimes enums may be stored as 'int' in the DB (if you have a legacy DB or want to save as much space as possible). This little example shows a way to store and retrieve JDK 1.5 enum to/from a SMALLINT column in the database. I selected a SMALLINT as, if you have more enums that the maximum value of a smallint... you got some other issues!

This example uses generics so the code required from one enum to the other is very small. This is inspired from http://www.hibernate.org/265.html

Beware that the enum.ordinal() relates to the ORDERING of the enum values, so, if your change it later on, all your DB values will return an incorrect value!

Here is our example enum

/** DO NOT RE-ORDER THEM otherwise you will lose the 
* correct mapping with the values in your DB */
public enum ContractState {
    UNAUTHORISED,
    OPEN,
    CLOSED;
}

and the Class using it is:

public class Contract {
    private int id;
...
    private ContractState state;

    public void setState(ContractState state) {
        this.state = state;
    }

    public ContractState getState() {
        return state;
    }
}

We need to define a special Hibernate mapping for that ContractState:

/** This class is used only in the hibernate XML configuration */
public class HibContractState extends IntEnumUserType<ContractStates> { 
    public HibContractState() { 
        // we must give the values of the enum to the parent.
        super(ContractState.class,ContractState.values()); 
    } 
}

and the Contract.hbm.xml file looks like this:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC 
  "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
  "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="bla.bla">
  <class name="Contract" lazy="true" table="contract">
    <id name="id">
      <generator class="native"/>
    </id>
.....        

    <property name="state" 
              type="net.objectlab.sandpit.hibernate.HibContractState" 
              not-null="true"/>
  </class>
</hibernate-mapping>

Et voila... that is all you need to do for each enum-to-int mapping that you may want, provided that you have the following code available in a library, this is the real logic...

import java.io.Serializable;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;

import org.hibernate.HibernateException;
import org.hibernate.usertype.UserType;

/**
 * Generic class to map a JDK 1.5 enum to a SMALL INT column in the DB.
 * Beware that the enum.ordinal() relates to the ORDERING of the enum values, so, if
 * your change it later on, all your DB values will return an incorrect value!
 * 
 * @author Benoit Xhenseval
 * @version 1
 */
public class IntEnumUserType<E extends Enum<E>> implements UserType { 
    private Class<E> clazz = null;
    private E[] theEnumValues;
    
    /**
     * Contrary to the example mapping to a VARCHAR, this would
     * @param c the class of the enum.
     * @param e The values of enum (by invoking .values()).
     */
    protected IntEnumUserType(Class<E> c, E[] e) { 
        this.clazz = c; 
        this.theEnumValues = e;
    } 
 
    private static final int[] SQL_TYPES = {Types.SMALLINT};
    
    /**
     * simple mapping to a SMALLINT.
     */
    public int[] sqlTypes() { 
        return SQL_TYPES; 
    } 
 
    public Class returnedClass() { 
        return clazz; 
    } 

    /**
     * From the SMALLINT in the DB, get the enum.  Because there is no
     * Enum.valueOf(class,int) method, we have to iterate through the given enum.values()
     * in order to find the correct "int".
     */
    public Object nullSafeGet(ResultSet resultSet, String[] names, Object owner) 
        throws HibernateException, SQLException { 
        final int val = resultSet.getShort(names[0]);
        E result = null;
        if (!resultSet.wasNull()) {
            try {
                for(int i=0; i < theEnumValues.length && result == null; i++) {
                    if (theEnumValues[i].ordinal() == val) {
                        result = theEnumValues[i];
                    }
                }
            } catch (SecurityException e) {
                result = null;
            } catch (IllegalArgumentException e) {
                result = null;
            }
        } 
        return result; 
    } 
 
    /**
     * set the SMALLINT in the DB based on enum.ordinal() value, BEWARE this
     * could change.
     */
    public void nullSafeSet(PreparedStatement preparedStatement, 
      Object value, int index) throws HibernateException, SQLException { 
        if (null == value) { 
            preparedStatement.setNull(index, Types.SMALLINT); 
        } else { 
            preparedStatement.setInt(index, ((Enum)value).ordinal()); 
        } 
    } 
 
    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); 
    } 
} 

This is probably not a great deal for Hibernate experts but may be useful to some of us. Feel free to use it, modify it, improve it and comment on it...and may be something like this should be included in the release?

regards

Benoit


  NEW COMMENT

Avoiding iteration 22 Nov 2005, 16:48 mxg75
Rather then iterating through theEnumValues each time nullSafeGet, would
it make more sence to sort the array one time durring the constructor,
so theEnumValues[i].ordinal()==i?  My idea:

<code>
	protected IntEnumUserType(Class<E> c, E[] e) { 
		this.clazz = c; 
		this.theEnumValues = (E[]) new Enum[e.length];
		for(E value:e) {
			e[value.ordinal()]=value;
		}
	} 

	public Object nullSafeGet(ResultSet resultSet, String[] names, Object
owner) 
		throws HibernateException, SQLException { 
		final int val = resultSet.getShort(names[0]);
		if (!resultSet.wasNull() && 0<=val && val<theEnumValues.length)
			return theEnumValues[val];
		else
			return null; 
	} 
</code>

Or am I missing some gotcha?  Seems like this could really speed up the
load, especially if you have a large number of enum constants.
 
May be I don't understand some potential issues but... 17 Mar 2006, 08:34 valenki-lite
public enum E {
 A,
 B;

 public final static int A_ID = 1;
 public final static int B_ID = 2;
 public final static int NONE_ID = 0;

 public int getId() {
  switch(this) {
   case A:
    return A_ID;
   case B:
    return B_ID;
   default:
    return NONE_ID;
  }  
 }

 public static int getNoneId() {
  return NONE_ID;
 } 

 public E getById(int id) {
  switch(id) {
   case A_ID:
    return A;
   case B_ID:
    return B;
   default:
    return null;
  }  
 }
}

public class C {
 private E e = E.A;
 private int eId = e.getId();

 public E getE() {
  fixE();
  return e;
 }
 public void setE(E e) {
  this.e = e;
  fixEId();
 }
 public int getEId() {
  fixEId();
  return eId;
 }
 public void setEId(int id) {
  eId = id;
  fixE();
 }

 private void fixE() {
  e = E.getById(eId);
 }
 private void fixEId() {
  if(e!=null) eId = e.getId();
  else eId = E.getNoneId();
 }
}
 
upd to prev.comment 17 Mar 2006, 08:37 valenki-lite
obviously, 
..
<parameter name="eId" type="integer" column="e_id" not-null="true"/>
..

and in the b-logic code use standard enum :)
 
Re: Avoiding iteration 15 Aug 2006, 15:47 crgardner
Here's one possible solution that is based on Gavin's EnumUserType
(http://www.hibernate.org/272.html).  EnumUserType allows using any Enum
without subclassing.  I changed his version to use the ordinals rather
than the String representations of enums.  Now I verified the persisting
works (at least in my simple environment), but haven't tried to map a
row back to an object.  In any event, here's my mapping doc:

  <class name="com.product.client.Client">
    <id name="id" column="CLIENT_ID">
      <generator class="native"/>
    </id>
    
    <property name="name" />
    
    <property name="clientType">
      <type name="com.product.hibernate.EnumUserType">
        <param    
             name="enumClassName">com.product.client.ClientType</param>
      </type>    
    </property>
  </class>

Here's the modified EnumUserType:

public class EnumUserType implements EnhancedUserType,
        ParameterizedType {

    private Class<Enum> enumClass;

    @SuppressWarnings("unchecked")
    public void setParameterValues(Properties parameters) {
        String enumClassName = parameters.getProperty("enumClassName");
        try {
            enumClass = (Class<Enum>) Class.forName(enumClassName);
        } catch (ClassNotFoundException cnfe) {
            throw new HibernateException("Enum class not found", cnfe);
        }
    }

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

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

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

    public boolean equals(Object x, Object y)
            throws HibernateException {
        return x == y;
    }

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

    public boolean isMutable() {
        return false;
    }

    @SuppressWarnings("unchecked")
    public Object nullSafeGet(ResultSet rs, String[] names,
            Object owner) throws HibernateException, SQLException {
        int ordinal = rs.getInt(names[0]);
        
        //Don't know if we are guaranteed to get the ordinals
        //in the right order with toArray() below.
        return rs.wasNull() ? null
                : EnumSet.allOf(enumClass).toArray()[ordinal];
    }

    public void nullSafeSet(PreparedStatement st, Object value,
            int index) throws HibernateException, SQLException {
        if (value == null) {
            st.setNull(index, Types.INTEGER);
        } else {
            st.setInt(index, ((Enum) value).ordinal());
        }
    }

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

    public Class returnedClass() {
        return enumClass;
    }

    public int[] sqlTypes() {
        return new int[] { Types.INTEGER };
    }

    @SuppressWarnings("unchecked")
    public Object fromXMLString(String xmlValue) {
        return Enum.valueOf(enumClass, xmlValue);
    }

    public String objectToSQLString(Object value) {
        return new StringBuffer(((Enum) value).ordinal()).toString();
    }

    public String toXMLString(Object value) {
        return objectToSQLString(value);
    }

}
 
ordinal - be careful 05 Jul 2007, 06:16 Lajcik
POST QUESTIONS ON THE FORUM! COMMENTS HERE SHOULD ADD VALUE TO THE 
PAGE!I would be careful with ordinals. Since it returns the index of 
the 
declaration in the enum  - the number returned may change if you add 
new values to the enum and youll get a mess when merging with old data.

To be safe you'd have to remember to add new values at the end of the 
enum and not to reorder your existing values. Good enough if you know 
that the enum wont change, but otherwise i would stick with the variant 
with .name()
 
© Copyright 2006, Red Hat Middleware, LLC. All rights reserved. JBoss and Hibernate are registered trademarks and servicemarks of Red Hat, Inc. [Privacy Policy]