Interview Questions

Using Enum with Hibernate

This article deals with using Enum types in Hibernate mapping.

Enum is a new data type added as part of JDK 1.5 mainly used to deal with constants in Java applications.

In the Hibernate mapping file, we usually map data types in Java bean to the columns in database tables.

For data types like String, Integer it is just a direct mapping to columns in database tables.

Consider the Gender.java Enum data type class below.

Gender.java

package in.techdive.hib.model.Gender;

public enum Gender
{
        MALE("M"), FEMALE("F");

        private String  description;

        Gender(String description)
        {
                this.description = description;
        }

        public String getDescription()
        {
                return new String(description);
        }

        public static Gender getGender(String value)
        {
                if ("M".equalsIgnoreCase(value))
                        return MALE;
                return FEMALE;
        }
}

To map Enum type to a column in DB , you need to create a new User Type by implementing org.hibernate.usertype.UserType and org.hibernate.usertype.ParameterizedType interfaces. The resulting class will be as follows.

EnumUserType.java

/**
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS''
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

package in.techdive.hib.EnumUserType;

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

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

import java.lang.reflect.Method;
import java.util.Properties;

import org.hibernate.type.NullableType;
import org.hibernate.type.TypeFactory;
import org.hibernate.usertype.ParameterizedType;

public class EnumUserType implements UserType, ParameterizedType
{
        private static final String             DEFAULT_IDENTIFIER_METHOD_NAME  = "name";
        private static final String             DEFAULT_VALUE_OF_METHOD_NAME    = "valueOf";

        private Class<? extends Enum>   enumClass;
        private Class<?>                                identifierType;
        private Method                                  identifierMethod;
        private Method                                  valueOfMethod;
        private NullableType                    type;
        private int[]                                   sqlTypes;

        public void setParameterValues(Properties parameters)
        {
                String enumClassName = parameters.getProperty("enumClass");
                try
                {
                        enumClass = Class.forName(enumClassName).asSubclass(Enum.class);
                }
                catch (ClassNotFoundException cfne)
                {
                        throw new HibernateException("Enum class not found", cfne);
                }

                String identifierMethodName = parameters.getProperty("identifierMethod", DEFAULT_IDENTIFIER_METHOD_NAME);

                try
                {
                        identifierMethod = enumClass.getMethod(identifierMethodName, new Class[0]);
                        identifierType = identifierMethod.getReturnType();
                }
                catch (Exception e)
                {
                        throw new HibernateException("Failed to obtain identifier method", e);
                }

                type = (NullableType) TypeFactory.basic(identifierType.getName());

                if (type == null)
                        throw new HibernateException("Unsupported identifier type " + identifierType.getName());

                sqlTypes = new int[] { Hibernate.STRING.sqlType() };

                String valueOfMethodName = parameters.getProperty("valueOfMethod", DEFAULT_VALUE_OF_METHOD_NAME);

                try
                {
                        valueOfMethod = enumClass.getMethod(valueOfMethodName, new Class[] { identifierType });
                }
                catch (Exception e)
                {
                        throw new HibernateException("Failed to obtain valueOf method", e);
                }
        }

        public Class returnedClass()
        {
                return enumClass;
        }

        public Object nullSafeGet(ResultSet rs, String[] names, Object owner) throws HibernateException, SQLException
        {
                Object identifier = type.get(rs, names[0]);
                if (rs.wasNull())
                {
                        return null;
                }

                try
                {
                        return valueOfMethod.invoke(enumClass, new Object[] { identifier });
                }
                catch (Exception e)
                {
                        throw new HibernateException("Exception while invoking valueOf method '" + valueOfMethod.getName()
                                        + "' of " + "enumeration class '" + enumClass + "'", e);
                }
        }

        public void nullSafeSet(PreparedStatement st, Object value, int index) throws HibernateException, SQLException
        {
                try
                {
                        if (value == null)
                        {
                                st.setNull(index, type.sqlType());
                        }
                        else
                        {
                                Object identifier = identifierMethod.invoke(value, new Object[0]);
                                type.set(st, identifier, index);
                        }
                }
                catch (Exception e)
                {
                        throw new HibernateException("Exception while invoking identifierMethod '" + identifierMethod.getName()
                                        + "' of " + "enumeration class '" + enumClass + "'", e);
                }
        }

        public int[] sqlTypes()
        {
                return sqlTypes;
        }

        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 (Serializable) 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;
        }

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

In the hibernate-cfg.xml file, you need to do the following changes.

hibernate-cfg.xml

<hibernate-mapping>
       <!-- for the param identifierMethod specify the method name which will return values of the enum to be stored in DB -->
        <!-- for the param valueOfMethod specify the method name in Gender class which will return the enum object -->
        <!-- these methods will be invoked using java reflection api in the EnumUserType class -->
   <typedef name="gen" class="in.techdive.hib.EnumUserType">
        <param name="enumClass">in.techdive.hib.model.Gender</param>
        <param name="identifierMethod">getDescription</param>
        <param name="valueOfMethod">getGender</param>
  </typedef>

  <property name="gender" type="gen">
    <column name="gender"/>
  </property>
 
  <!-- specify other mapping beans here -->
   
  </hibernate-mapping>

After making the above changes, when you run your Hibernate application, for Gender enum type "M" and "F" will be stored for Enum values Gender.MALE and Gender.FEMALE respectively in DB.