Sunday, August 19, 2012

Hibernate enum mapping - hmb

The following example shows how to map enums using hbm files. Media is the enum which is mapped in this means. It has a 'description' attribute which will be saved in the database. The methods 'getDescription()' and 'getMedia(String)' are used to retrieve the description and the media of 'Media' respectively. Notice the latter is static and returns corresponding enum type of the value passed into it.

==============================================================
package com.shyarmal.hibernate.example.model;

public enum Media {

    PRINTED("PRINTED"), ELECTRONIC("ELECTRONIC"), UNKNOWN("UNKNOWN");
    private String description;

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

    public String getDescription() {
        return description;
    }

    public static Media getMedia(String value) {
        if ("PRINTED".equals(value)) {
           return PRINTED;
        } else if ("ELECTRONIC".equals(value)) {
           return ELECTRONIC;
        } else {
            return UNKNOWN;
        }
    }
}
==============================================================

A custom enum user type is defined below, implementing the interfaces UserType and ParameterizedType. Parameters of the typedef element of the hbm (found below) are used in 'setParameterValues(Properties)' to initialize the enum type and it's methods. The methods  nullSafeGet(ResultSet rs, String[] names, Object owner)and nullSafeGet(ResultSet rs, String[] names, Object owner) are used to get and set the enum from and to the database.

==============================================================

package com.shyarmal.hibernate.example.model;

import org.hibernate.Hibernate;
import org.hibernate.HibernateException;
import org.hibernate.type.NullableType;
import org.hibernate.type.TypeFactory;
import org.hibernate.usertype.ParameterizedType;
import org.hibernate.usertype.UserType;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties;

public class EnumUserType implements UserType, ParameterizedType {

    private static final String IDENTIFIER_METHOD = "name";
    private static final String VALUE_OF_METHOD = "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", IDENTIFIER_METHOD);
        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", VALUE_OF_METHOD);
        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("Error in valueOf method '" + valueOfMethod.getName()
                    + "' of " + "enum 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("Error in identifierMethod '" + identifierMethod.getName()
                    + "' of " + "enum 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;
    }
}

==============================================================

I have posted the hbm file below. Only the configurations relevant to the type mapping is present. A type should be defined first, which is done with the 'typedef' element. So a type, 'media' is defined providing the class above, EnumUserType. Parameters 'enumClass', 'identifierMethod' and 'valueOfMethod' are defined. The parameter 'enumClass' is our enum, Media. Other two parameters, identifierMethod and valueOfMethod are the methods getDescription() and getMedia(String) respectively.

==============================================================

<?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="com.shyarmal.hibernate.example.model">

    <typedef name="media" class="com.shyarmal.hibernate.example.model.EnumUserType">
        <param name="enumClass">com.shyarmal.hibernate.example.model.Media</param>
        <param name="identifierMethod">getDescription</param>
        <param name="valueOfMethod">getMedia</param>
    </typedef>

    <class name="Xxxxx" table="XXXX_XXX">
        <!--
            ---- Other property definitions. ----
        -->           
        <property name="media" column="MEDIA" type="media"/>
    </class>

</hibernate-mapping>

==============================================================

After defining a type as described above, a property should be defined for the class (here it's Xxxxx), which should have an attribute called media of type 'Media' (our enum). So '<property name="media" column="MEDIA" type="media"/>' says that the property 'media' of the class Xxxxx of type 'media' (our defined type using typedef) will be mapped to 'MEDIA' column of 'XXXX_XXX' table in the database.


reference: using-enum-hibernate

thanks,
Shyarmal.

No comments:

Post a Comment