Wednesday, July 27, 2011

Hibernate and enum handling


Hibernate and Enumeration

Enumeration is widely used in model classes.Hibernate has inbuilt support for non parameterized enumeration.However for parameterized enumeration,we have to provide an User Type implementation implementing UserType and Parameterized Type.

Plain Enumeration in Hibernate

Lets take a simple case of plain enumeration.Here we are creating a non parameterized enumeration for educational qualification and using it in the model class.

SQL statements

-- Database entity showing normal enum handling by hibernate
create table ORM_PersonEduProfile(
 Id bigint Identity(1,1) not null primary key,
 Name varchar(50) not null,
 Address varchar(50) not null,
 EduQualification varchar(10) not null
)


EduEnum.java having enumeration values for High School,Under Graduate,Post Graduate and PhD
package com.kunaal.model.plainEnum;

/**
 * Enum for High School,UG,PG,PHD
 * 
 * @author Kunaal A Trehan
 *
 */
public enum EduEnum {
 HS,
 UG,
 PG,
 PHD;
}


If we have to use such plain enums in Hibernate,we just have to define @Enumerated annotation on the corresponding field.
We are going to use EduEnum in PersonEduEnum.java

PersonEduQual.java -In this class we have eduQual property mapped to EduEnum.In order to tell the hibernate that property is mapped to an enumeration.We just have to provide @Enumerated annotation

package com.kunaal.model.plainEnum;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

import com.kunaal.model.BaseModel;


/**
 * Model class showing usage of simple enumeration
 * @author Kunaal A Trehan
 *
 */
@Entity
@Table(name="ORM_PersonEduProfile")
public class PersonEduQual implements BaseModel<Long>{
 
 @Id
 @GeneratedValue(strategy=GenerationType.IDENTITY)
 @Column(name="id")
 private Long id;
 
 @Column(name="name")
 private String name;
 
 @Column(name="address")
 private String address;
 
 @Enumerated(EnumType.STRING)
 @Column(name="eduQualification")
 private EduEnum eduQual;

 /**
  * @return the id
  */
 public Long getId() {
  return id;
 }

 /**
  * @param id the id to set
  */
 public void setId(Long id) {
  this.id = id;
 }

 /**
  * @return the name
  */
 public String getName() {
  return name;
 }

 /**
  * @param name the name to set
  */
 public void setName(String name) {
  this.name = name;
 }

 /**
  * @return the address
  */
 public String getAddress() {
  return address;
 }

 /**
  * @param address the address to set
  */
 public void setAddress(String address) {
  this.address = address;
 }

 /**
  * @return the eduQual
  */
 public EduEnum getEduQual() {
  return eduQual;
 }

 /**
  * @param eduQual the eduQual to set
  */
 public void setEduQual(EduEnum eduQual) {
  this.eduQual = eduQual;
 }

 /* (non-Javadoc)
  * @see java.lang.Object#hashCode()
  */
 @Override
 public int hashCode() {
  final int prime = 31;
  int result = 1;
  result = prime * result + ((id == null) ? 0 : id.hashCode());
  return result;
 }

 /* (non-Javadoc)
  * @see java.lang.Object#equals(java.lang.Object)
  */
 @Override
 public boolean equals(Object obj) {
  if (this == obj)
   return true;
  if (obj == null)
   return false;
  if (getClass() != obj.getClass())
   return false;
  PersonEduQual other = (PersonEduQual) obj;
  if (id == null) {
   if (other.id != null)
    return false;
  } else if (!id.equals(other.id))
   return false;
  return true;
 }

 public Long getPrimaryKey() {
  return getId();
 }

}


So using plain enumeration is very simple.By providing the @Enumerated annotation,hibernate knows that it has to fetch the value from enumeration and to create an enumeration when fetching the information from database.Internally Hibernate uses an user type for doing this.


Paramterized Enumeration in Hibernate

There are some cases when we have to show different information in the UI and persist different information in the database.In that case we use parameterized enumeration.

Lets take the use case of Gender.We want to show MALE in the UI.But persist it as 'M' in the database.Similarly for FEMALE,we want to persist it as 'F' in database.Inorder to support it we create a parameterized user type.

SQL Statements:-

-- Database entity showing custom enum mapping handling by hibernate
create table ORM_EnumPerson(
 Id bigint Identity(1,1) not null primary key,
 Name varchar(50) not null,
 Sex char(1) not null,
 Age int not null,
 DOB datetime not null,
 Address varchar(250) not null
)


GenderEnum.java- Implementation of Parameterized enumeration
Here we are storing MALE as 'M' and FEMALE as 'F'

package com.kunaal.model.customEnum;

/**
 * Custom enum type where UI and DB have different values
 * for same entity
 * 
 * @author Kunaal A Trehan
 *
 */
public enum GenderEnum {
 
 MALE("M"),
 FEMALE("F");
 
 /**
  * Private gender code  
  */
 private String genderCode;
 
 private GenderEnum(String val){
  this.genderCode=val;
 }

 /**
  * @return the genderCode
  */
 public String getGenderCode() {
  return genderCode;
 }

 /**
  * @param genderCode the genderCode to set
  */
 public void setGenderCode(String genderCode) {
  this.genderCode = genderCode;
 }
 
 /**
  * Utility method for creating enumeration from the db represented value
  * 
  * @param value
  * @return
  */
 public static GenderEnum recreateEnum(String value){
  GenderEnum enumVal=null;
  
  if(value !=null){
   if(value.equalsIgnoreCase("M"))
    enumVal=GenderEnum.MALE;
   else if(value.equalsIgnoreCase("F"))
    enumVal=GenderEnum.FEMALE;
  }
  
  return enumVal;
 }
 
 /**
  * Utility method for inserting string value in db from enumeration
  * 
  * @return
  */
 public String recreateString(){
  return genderCode;
 }
 
 
}


EnumUserType.java-Parameterized user type for handling parameterized enum.In this user type,we pass some parameters which we use in user type for doing parameterized  enumeration conversion.

In this UserType,we pass parameters like method to create enumeration,method to fetch value to persist in database and actual enumeration class which needs to be looked up for method implementations

package com.kunaal.model.customEnum;

import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.Properties;

import org.hibernate.HibernateException;
import org.hibernate.usertype.ParameterizedType;
import org.hibernate.usertype.UserType;
import org.springframework.util.ObjectUtils;

import com.kunaal.model.customEnum.GenderEnum;

/**
 * Parameterized generalized user type for enum handling
 * It can handle any type of enum as long db data type is 
 * character.
 * We can have one more parameter and get rid of this bottleneck also
 * 
 * @author Kunaal A Trehan
 *
 */
public class EnumUserType implements UserType,ParameterizedType{
 
 private Method recreateEnumMthd;
 
 private Method recreateStringMthd;
 
 private Class enumClass;

 /**
  * This method uses the parameter values passed during enum mapping definition 
  * and sets corresponding properties defined
  */
 public void setParameterValues(Properties parameters) {
  if(parameters !=null){
   String enumMthd = parameters.getProperty("recreateEnumMthd");
   String strMthd=parameters.getProperty("recreateStringMthd");
   String className=parameters.getProperty("enumClassName");
   Class<?> returnType =null;
   
   try {
    enumClass=Class.forName(className);
    recreateStringMthd = enumClass.getMethod(strMthd, new Class[]{});
    returnType = recreateStringMthd.getReturnType();
    recreateEnumMthd=enumClass.getMethod(enumMthd, new Class[]{returnType});
   } catch (ClassNotFoundException e) {
    e.printStackTrace();
   } catch (SecurityException e) {
    e.printStackTrace();
   } catch (NoSuchMethodException e) {
    e.printStackTrace();
   }
  }
 }

 /**
  * This method maps the database mapping
  */
 public int[] sqlTypes() {
  return new int[]{Types.CHAR};
 }

 /**
  * This method maps the class for which user type is created
  */
 public Class returnedClass() {
  //return GenderEnum.class;
  return enumClass;
 }

 
 public boolean equals(Object x, Object y) throws HibernateException {
  return ObjectUtils.nullSafeEquals(x, y);
 }

 /**
  * Fetch the hash code
  */
 public int hashCode(Object x) throws HibernateException {
  return x.hashCode();
 }

 /**
  * Recreate the enum from the resultset
  */
 public Object nullSafeGet(ResultSet rs, String[] names, Object owner)
   throws HibernateException, SQLException {
  
  String value = rs.getString(names[0]);
  Object returnVal=null;
  
  if(value==null)
   return null;
  else{
   try {
    returnVal = recreateEnumMthd.invoke(enumClass, new Object[]{value});
   } catch (IllegalArgumentException e) {
    e.printStackTrace();
   } catch (IllegalAccessException e) {
    e.printStackTrace();
   } catch (InvocationTargetException e) {
    e.printStackTrace();
   }
  }
  //return (GenderEnum)returnVal;
  return returnVal;
 }

 /**
  * Fetch the data from enum and set it in prepared statement
  */
 public void nullSafeSet(PreparedStatement st, Object value, int index)
   throws HibernateException, SQLException {
  String prepStmtVal=null;
  
  if(value ==null){
   st.setObject(index, null);
  }else{
   try {
    prepStmtVal = (String)recreateStringMthd.invoke(value, new Object[]{});
    st.setString(index, prepStmtVal);
   } catch (IllegalArgumentException e) {
    e.printStackTrace();
   } catch (IllegalAccessException e) {
    e.printStackTrace();
   } catch (InvocationTargetException e) {
    e.printStackTrace();
   }
  }
 }

 /**
  * Deep copy method
  */
 public Object deepCopy(Object value) throws HibernateException {
  if (value==null)
   return null;
  else{
   GenderEnum enumVal=(GenderEnum)value;
   return GenderEnum.recreateEnum(enumVal.getGenderCode());
  }
 }

 public boolean isMutable() {
  return false;
 }

 public Serializable disassemble(Object value) throws HibernateException {
  Object  deepCopy=deepCopy(value);
  
  if(!(deepCopy instanceof Serializable))
   return (Serializable)deepCopy;
  
  return null;
 }

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

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

}


In order to use the paramterized enum and corresponding usertype in the class.We define the user type and pass the paramters.In the EnumPerson class look at the genderEnum property where we are providing this mapping.

EnumPerson.java using the paramterized enum for genderEnum property.


package com.kunaal.model.customEnum;

import java.util.Date;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

import org.hibernate.annotations.Parameter;
import org.hibernate.annotations.Type;

import com.kunaal.model.BaseModel;
import com.kunaal.model.customEnum.GenderEnum;

/**
 * 
 * Model class showing custom Enum implementation .
 *  
 * @author Kunaal A Trehan
 *
 */
@Entity
@Table(name="ORM_EnumPerson")
public class EnumPerson implements BaseModel<Long>{

 @Id
 @GeneratedValue(strategy=GenerationType.IDENTITY)
 @Column(name="id")
 private Long personId;
 
 @Column(name="name")
 private String name;
 
 @Type(type="com.kunaal.model.customEnum.EnumUserType",
   parameters={
      @Parameter(name="enumClassName",value="com.kunaal.model.customEnum.GenderEnum"),
      @Parameter(name="recreateEnumMthd",value="recreateEnum"),
      @Parameter(name="recreateStringMthd",value="recreateString")
   }
  )
 @Column(name="sex")
 private GenderEnum genderEnum;
 
 @Column(name="age")
 private Integer age;
 
 @Column(name="dob")
 private Date dob;
 
 @Column(name="address")
 private String address;

 /**
  * @return the personId
  */
 public Long getPersonId() {
  return personId;
 }

 /**
  * @param personId the personId to set
  */
 public void setPersonId(Long personId) {
  this.personId = personId;
 }

 /**
  * @return the name
  */
 public String getName() {
  return name;
 }

 /**
  * @param name the name to set
  */
 public void setName(String name) {
  this.name = name;
 }

 /**
  * @return the genderEnum
  */
 public GenderEnum getGenderEnum() {
  return genderEnum;
 }

 /**
  * @param genderEnum the genderEnum to set
  */
 public void setGenderEnum(GenderEnum genderEnum) {
  this.genderEnum = genderEnum;
 }

 /**
  * @return the age
  */
 public Integer getAge() {
  return age;
 }

 /**
  * @param age the age to set
  */
 public void setAge(Integer age) {
  this.age = age;
 }

 /**
  * @return the dob
  */
 public Date getDob() {
  return dob;
 }

 /**
  * @param dob the dob to set
  */
 public void setDob(Date dob) {
  this.dob = dob;
 }

 /**
  * @return the address
  */
 public String getAddress() {
  return address;
 }

 /**
  * @param address the address to set
  */
 public void setAddress(String address) {
  this.address = address;
 }

 /* (non-Javadoc)
  * @see java.lang.Object#hashCode()
  */
 @Override
 public int hashCode() {
  final int prime = 31;
  int result = 1;
  result = prime * result
    + ((personId == null) ? 0 : personId.hashCode());
  return result;
 }

 /* (non-Javadoc)
  * @see java.lang.Object#equals(java.lang.Object)
  */
 @Override
 public boolean equals(Object obj) {
  if (this == obj)
   return true;
  if (obj == null)
   return false;
  if (getClass() != obj.getClass())
   return false;
  EnumPerson other = (EnumPerson) obj;
  if (personId == null) {
   if (other.personId != null)
    return false;
  } else if (!personId.equals(other.personId))
   return false;
  return true;
 }

 /* (non-Javadoc)
  * @see java.lang.Object#toString()
  */
 @Override
 public String toString() {
  return "EnumPerson [personId=" + personId + ", name=" + name
    + ", genderEnum=" + genderEnum + ", age=" + age + ", dob="
    + dob + ", address=" + address + "]";
 }

 public Long getPrimaryKey() {
  return getPersonId();
 }
}

4 comments:

  1. Excellent post!
    You need to change the "deepCopy(Object value)" method of the EnumUserType class, because it currently contains a hard reference to "GenderEnum". This makes the EnumUserType not re-usable for other enumerations.

    ReplyDelete
  2. You lost me at the setter within the enum

    ReplyDelete
  3. Thanks for the post. Just to fill in some missing details:

    For the deepCopy method, I think you can just return the object passed in, since this UserType is for an enum, and enums are immutable.

    For disassemble, you have a negation that should be removed. I just did this:
    return (o instanceof Serializable) ? (Serializable) deepCopy(o) : null;



    ReplyDelete