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.

Sunday, August 12, 2012

Hibernate Hierarchy Mapping - Table Per Class

This example shows how to map a hierarchy with hibernate using the strategy, table per class. Circle and Square are two classes which extend Shape. The attribute, id of Shape is common to both the subclasses and will be the primary key of the tables created.

===================================================================
// The shape class
package com.shyarmal.hibernate.example.shape;

public class Shape implements IPersistable {

    protected long id;

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }
}


// Subclass of Shape, Circle
package com.shyarmal.hibernate.example.shape;

public class Circle extends Shape {

    private float radius;
    private float centerX;
    private float centerY;

    public float getRadius() {
        return radius;
    }

    public void setRadius(float radius) {
        this.radius = radius;
    }

    public float getCenterX() {
        return centerX;
    }

    public void setCenterX(float centerX) {
        this.centerX = centerX;
    }

    public float getCenterY() {
        return centerY;
    }

    public void setCenterY(float centerY) {
        this.centerY = centerY;
    }
}


// Subclass of Shape, Square
package com.shyarmal.hibernate.example.shape;

public class Square extends Shape {

    private float length;
    private float topX;
    private float topY;

    public float getTopX() {
        return topX;
    }

    public void setTopX(float topX) {
        this.topX = topX;
    }

    public float getlength() {
        return length;
    }

    public void setlength(float length) {
        this.length = length;
    }

    public float getTopY() {
        return topY;
    }
    public void setTopY(float topY) {
        this.topY = topY;
    }
}

===================================================================
The hibernate xml mapping for the above hierarchy is as follows. All three classes mapping configurations are done in the same hmb file. This configuration will map (and/or create) three tables for the three classes.

===================================================================
<?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.shape">
    <class name="Shape" table="SHAPE">
        <id name="id" column="ID" type="long">
            <generator class="native" />
        </id>
        <joined-subclass table="CIRCLE" name="Circle">
            <key column="ID" />
            <property name="radius" column="RADIUS" type="float"/>
            <property name="centerX" column="CENTER_X" type="float"/>
            <property name="centerY" column="CENTER_Y" type="float"/>
        </joined-subclass>
        <joined-subclass table="SQUARE" name="Square">
            <key column="ID" />       
            <property name="length" type="float" column="LENGTH"/>
            <property name="topY" column="TOP_Y" type="float" />
            <property name="topX" column="TOP_X" type="float" />
        </joined-subclass>
    </class>
</hibernate-mapping>

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

As you can notice, the 'class' tag refers to the class, Shape and the primary key is it's id attribute  (mapped with 'generator' tag). Subclasses are mapped with the element, 'joined-subclass', enclosed in 'class' tag. The element, 'key' defines a (foreign key) reference to the 'ID' column of the 'SHAPE' table. Further, the properties specific to each subclass is defined within the 'joined-subclass' element.

thanks,
Shyarmal.