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

Tuesday, July 26, 2011

Hibernate and User Types


What is user Type?

There are many cases where we persist/update the information differently and show it differently in the UI.

Lets take the case of gender information,in database we are persisting 'M' for 'Male' and 'F' for 'Female'.So we are persisting and showing the information differently.

Another use case could be Phone Number.We are showing area code and number differently in UI.However in database we are persisting area code and phone number together.
Hibernate helps in resolving these cases by providing user types which helps in this conversion.

Simple UserType

UseCase:- In the UI we are showing area code and phone number seperately.However in  the database,we are persisting the area code and phone number together in one column as a String datatype.

Phone.java containing seperate place holders for area code and phone number.

package com.kunaal.model.userType;

/**
 * 
 * User type class converting area code and phone number into one column in database
 * and vice versa.
 * In the UI area code and phone number have seperate place holders 
 * But in database it wraps into one column.
 * 
 * @author Kunaal A Trehan
 *
 */
public class Phone {
 
 private String areaCode;
 
 private String phoneNum;

 /**
  * @return the areaCode
  */
 public String getAreaCode() {
  return areaCode;
 }

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

 /**
  * @return the phoneNum
  */
 public String getPhoneNum() {
  return phoneNum;
 }

 /**
  * @param phoneNum the phoneNum to set
  */
 public void setPhoneNum(String phoneNum) {
  this.phoneNum = phoneNum;
 }
 
 public String toString(){
  return areaCode + "-" + phoneNum;
 }

 /* (non-Javadoc)
  * @see java.lang.Object#hashCode()
  */
 @Override
 public int hashCode() {
  final int prime = 31;
  int result = 1;
  result = prime * result
    + ((areaCode == null) ? 0 : areaCode.hashCode());
  result = prime * result
    + ((phoneNum == null) ? 0 : phoneNum.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;
  Phone other = (Phone) obj;
  if (areaCode == null) {
   if (other.areaCode != null)
    return false;
  } else if (!areaCode.equals(other.areaCode))
   return false;
  if (phoneNum == null) {
   if (other.phoneNum != null)
    return false;
  } else if (!phoneNum.equals(other.phoneNum))
   return false;
  return true;
 }

}


PhoneUserType.java which implements UserType interface provided by hibernate.UserType implementation provides developer to hook the custom implementation and acts as a adapter between relational database and UI view.Important methods are nullSafeGet and nullSafeSet where all the conversion logic exists

package com.kunaal.model.userType;

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

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

/**
 * @author Kunaal A Trehan
 *
 */
public class PhoneUserType implements UserType {

 /**
  * What column types to map,data type of the column
  */
 public int[] sqlTypes() {
  return new int[]{Types.VARCHAR};
 }

 /**
  * Class  details of object which is going to be used
  */
 public Class returnedClass() {
  return Phone.class;
 }

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

 public int hashCode(Object x) throws HibernateException {
  if (x!=null)
   return x.hashCode();
  else
   return 0;
 }

 /**
  * Creates the custom object from the data returned by resultset
  */
 public Object nullSafeGet(ResultSet rs, String[] names, Object owner)
   throws HibernateException, SQLException {
  Phone phone=null;
  
  String nameVal = rs.getString(names[0]);
  if(nameVal !=null){
   phone=new Phone();
   
   StringTokenizer tokenizer=new StringTokenizer(nameVal,"-");
   phone.setAreaCode(tokenizer.nextToken());
   phone.setPhoneNum(tokenizer.nextToken());
   
  }
  return phone;
 }

 /**
  * Converts custom object into value which needs to be passed to prepared statement
  */
 public void nullSafeSet(PreparedStatement st, Object value, int index)
   throws HibernateException, SQLException {
  
  if(value==null){
   st.setNull(index, Types.VARCHAR);
  }else{
   st.setString(index, ((Phone)value).toString());
  }
  
 }

 /**
  * Returns deep copy of object
  */
 public Object deepCopy(Object value) throws HibernateException {
  if(value==null)
   return null;
  else{
   Phone newObj=new Phone();
   Phone existObj=(Phone)value;
   
   newObj.setAreaCode(existObj.getAreaCode());
   newObj.setPhoneNum(existObj.getPhoneNum());
   
   return newObj;
  }
   
 }

 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);
  //return cached;
 }

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

}


Inorder to use PhoneUserType ,we have to add @Type annotation defining the implementation class

@Type (type="com.kunaal.model.userType.PhoneUserType")
private Phone phoneNumber;

Composite UserType

Plain usertypes are used when we are storing the information in one column in database.However there are cases when we have multiple columns corresponding to one java object.In that case,hibernate provides composite user type to do the conversion.

Use Case:- Lets take the case of monetary value.We have a money class containing currency code and monetary amount.In  database we have different columns for persisting this information.However when we show the information in the UI,we are clubbing the property values and showing it.

e.g. In the UI,100 US dollars are shown as USD 100.However in database USD and 100 are stored seperately in different columns.

Money.java having seperate properties for currency code and amount.


package com.kunaal.model.compositeUserType;

import java.util.Currency;

/**
 * Custom user type for money
 * In UI there will be one placeholder for entering monetary value
 * including currency code and amount.
 * However in db there are two different column for that 
 *
 * @author Kunaal A Trehan
 *
 */
public class Money {

 private Currency currCode;
 
 private Double amount;

 /**
  * @return the currCode
  */
 public Currency getCurrCode() {
  return currCode;
 }

 /**
  * @param currCode the currCode to set
  */
 public void setCurrCode(Currency currCode) {
  this.currCode = currCode;
 }

 /**
  * 
  */
 public Money() {
  super();
 }

 /**
  * @param currCode
  * @param amount
  */
 public Money(Currency currCode, Double amount) {
  super();
  this.currCode = currCode;
  this.amount = amount;
 }

 /**
  * @return the amount
  */
 public Double getAmount() {
  return amount;
 }

 /**
  * @param amount the amount to set
  */
 public void setAmount(Double amount) {
  this.amount = amount;
 }

 /* (non-Javadoc)
  * @see java.lang.Object#hashCode()
  */
 @Override
 public int hashCode() {
  final int prime = 31;
  int result = 1;
  result = prime * result + ((amount == null) ? 0 : amount.hashCode());
  result = prime * result
    + ((currCode == null) ? 0 : currCode.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;
  Money other = (Money) obj;
  if (amount == null) {
   if (other.amount != null)
    return false;
  } else if (!amount.equals(other.amount))
   return false;
  if (currCode == null) {
   if (other.currCode != null)
    return false;
  } else if (!currCode.equals(other.currCode))
   return false;
  return true;
 }
 
}


MoneyUserType.java containing implementation for conversion from Object to relational database and vice versa.Important methods are getPropertyNames(),getPropertyTypes(),getPropertyValue(),setPropertyValue(),nullSafeGet(),nullSafeSet() 
package com.kunaal.model.compositeUserType;

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

import org.hibernate.HibernateException;
import org.hibernate.engine.SessionImplementor;
import org.hibernate.type.Type;
import org.hibernate.usertype.CompositeUserType;
import org.springframework.util.ObjectUtils;

/**
 * Composite user type mapping money object to two columns in database
 * 
 * @author Kunaal A Trehan
 *
 */
public class MoneyUserType implements CompositeUserType{
 /**
  * This refers to java object property names which are mapped 
  */
 public String[] getPropertyNames() {
  return new String[]{"currCode","amount"};
 }

 /**
  * This refers to java object property types
  */
 public Type[] getPropertyTypes() {
  return new Type[]{org.hibernate.type.StandardBasicTypes.CURRENCY,
    org.hibernate.type.StandardBasicTypes.DOUBLE};
 }

 /**
  * This method fetches the property from the user type depending upon 
  * the index.It should follow getPropertyNames()s
  */
 public Object getPropertyValue(Object component, int property)
   throws HibernateException {
  if(component ==null)
   return null;
  else{
   if(property==0 )
    return ((Money)component).getCurrCode();
   else if(property==1)
    return ((Money)component).getAmount();
  }
  
  return null;
 }

 /**
  * This method sets the individual property in the custom user type
  */
 public void setPropertyValue(Object component, int property, Object value)
   throws HibernateException {
  if(value!=null){
   if (property ==0){
    ((Money)component).setCurrCode((Currency)value);
   }else if(property ==1)
    ((Money)component).setAmount((Double)value);
  }
 }

 /** 
  * This method returns the custom user type class
  */
 public Class returnedClass() {
  return Money.class;
 }

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

 public int hashCode(Object x) throws HibernateException {
  if (x!=null)
   return x.hashCode();
  else
   return 0;
 }

 /**
  * This method constructs the custom user type from the resultset
  */
 public Object nullSafeGet(ResultSet rs, String[] names,
   SessionImplementor session, Object owner)
   throws HibernateException, SQLException {
  String currCode = rs.getString(names[0]);
  Double balanceAmt=rs.getDouble(names[1]);
  
  if(currCode !=null && balanceAmt !=null){
   return new Money(Currency.getInstance(currCode),balanceAmt);
  }else{
   return null;
  }
 }

 /**
  * This method sets the value from the user type into prepared statement
  */
 public void nullSafeSet(PreparedStatement st, Object value, int index,
   SessionImplementor session) throws HibernateException, SQLException {
  if(value !=null){
   st.setString(index,((Money)value).getCurrCode().getCurrencyCode());
   st.setDouble(index+1, ((Money)value).getAmount());
  }else{
   st.setObject(index,null);
   st.setObject(index +1, null);
  }
 }

 /**
  * Deep copy
  */
 public Object deepCopy(Object value) throws HibernateException {
  Money returnVal=new Money();
  Money currVal=(Money)value;
  Double amount = currVal.getAmount();
  Currency currCode = currVal.getCurrCode();
  
  returnVal.setCurrCode(Currency.getInstance(currCode.getCurrencyCode()));
  returnVal.setAmount(new Double(amount.doubleValue()));
  
  return returnVal;
 }

 public boolean isMutable() {
  return false;
 }

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

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

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

}


Inorder to use MoneyUserType ,we have to add @Type annotation defining the implementation class and column mapping

@Type(type="com.kunaal.model.compositeUserType.MoneyUserType")
                     @Columns(columns = { @Column(name="CurrencyCode"),
                                                           @Column(name="Balance")
                                                         }
                                        )
private Money acctBalance;

Uni directional,Bi directional,Cascade and MappedBy Hibernate

Unidirectional and Bidirectional relationship

In order to understand this relationship take the case of Book Library.
Book Library contains collection of books. So book contains foreign key of book library
to maintain the relationship.

Unidirectional relationship refers to the case when relationship between two objects can be accessed by one way only. i.e
From Book Library we can access books not vice versa
class Book{
     Long bookId;
     String bookName;
      …........
}

class BookLibrary{
      Long libraryId;
      String libraryName;
       Set<Book> bookSet;
}


Bidirectional relationship refers to the case when relationship between two objects can be accessed both ways. i.e
From Book Library we can access collection of Books and Book Object will have reference to BookLibrary

class Book{
     Long bookId;
     String bookName;
     BookLibrary library;
      …........
}
Though in both the cases for relationship to occur there would be foreign key mapping in Book Table corresponding to Book Library.


Cascade By Attribute

Taking the Book Library example further,lets assume we have a form/web page where we input the information regarding the library and books. So we have a library object containing library information and collection of books. In traditional JDBC approach,we will persist the Book Library first ,get the corresponding primary key and then iterate through book list and persist it one by one by adding the foreign key attribute. All this work has to be done by developer.
 

Similarly for updates and deletes we have to follow the same procedure.
Cascade By attribute provided by Hibernate keeps the above mechanism abstract to the developer. Developer persists/updates/deletes the top level object and changes are
propagated to the associated entities depending upon the value of cascade by attribute.

So taking the above example if we provide the cascade by attribute to the book set as
ALL. Whatever operations are done on BookLibrary object ,it will be passed on to the book collection object also. Developer need not have to code this,it will happen automatically.

So by providing the right cascade attributes we can govern the flow of changes from parent to child entities.

class BookLibrary{
      Long libraryId;
      String libraryName;
       …...
    @Cascade(value = { CascadeType.ALL })
       Set<Book> bookSet;
}


MappedBy Attribute

MappedBy Attribute is the most misunderstood concept. It comes into action when we have bi directional relationship between the entities. In relational database we have foreign key attribute which identifies the parent and child tables.

However when we have bi-directional relationship,in order to provide the identification regarding the same hibernate has come up with mappedBy attribute.

Normally mappedBy attribute is provided on the object which does not contains foreign key.
In hibernate terms,it means “I am not the owner of the relationship,ownership is governed by other entity'.

Its the job of the associated entity on non mappedBy side to maintain the association. In case other entity does not do the association,there will be NULL in the foreign key column.

Inorder to understand it better lets take case of  parent and child.Child belongs to a parent and parent can have more than one child.Its a classical case of one to many relationship.
Here mappedBy attribute is at parent side.So its the responsibilty of child to maintain the realtionship.

SQL Script

create table ORM_MappedBy_Parent(
    Id bigint Identity(1,1) primary key,
    ParentName varchar(50) not null,
    ParentAddr varchar(50)not null
)

create table ORM_MappedBy_Child(
 ChildId bigint identity(1,1) primary key,
 ChildName varchar(50) not null,
 ParentId bigint  null
)

alter table ORM_MappedBy_Child 
ADD FOREIGN KEY (ParentId) REFERENCES ORM_MappedBy_Parent(Id);


Here ORM_MappedBy_Child contains the foriegn key.So  Parent will have collection of child elements.

Model Classes

MappedByParent.java containing collection of Child entities with mappedBy attribute


package com.kunaal.model.mappedBy;

import java.util.HashSet;
import java.util.Set;

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

import com.kunaal.model.BaseModel;

/**
 * @author Kunaal A Trehan
 * 
 */
@Entity
@Table(name="ORM_MappedBy_Parent")
public class MappedByParent implements BaseModel<Long>{

 @Id
 @GeneratedValue(strategy=GenerationType.IDENTITY)
 @Column(name="id")
 private Long parentId;
 
 @Column(name="parentName")
 private String name;
 
 @Column(name="parentAddr")
 private String address;

 @OneToMany(cascade=CascadeType.ALL,mappedBy="parent")
 private Set<MappedByChild> childSet=new HashSet<MappedByChild>();
 
 public Long getPrimaryKey() {
  return getParentId();
 }

 /**
  * @return the parentId
  */
 public Long getParentId() {
  return parentId;
 }

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

 /**
  * @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;
 }

 /* (non-Javadoc)
  * @see java.lang.Object#hashCode()
  */
 @Override
 public int hashCode() {
  final int prime = 31;
  int result = 1;
  result = prime * result
    + ((parentId == null) ? 0 : parentId.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;
  MappedByParent other = (MappedByParent) obj;
  if (parentId == null) {
   if (other.parentId != null)
    return false;
  } else if (!parentId.equals(other.parentId))
   return false;
  return true;
 }

 /**
  * @return the childSet
  */
 public Set<MappedByChild> getChildSet() {
  return childSet;
 }

 /**
  * @param childSet the childSet to set
  */
 public void setChildSet(Set<MappedByChild> childSet) {
  this.childSet = childSet;
 }
 

}


MappedByChild.java which is owner of the relationship


package com.kunaal.model.mappedBy;

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

import com.kunaal.model.BaseModel;

/**
 * 
 * @author Kunaal A Trehan
 */
@Entity
@Table(name="ORM_MappedBy_Child")
public class MappedByChild implements BaseModel<Long>{

 @Id
 @GeneratedValue(strategy=GenerationType.IDENTITY)
 @Column(name="childId")
 private Long childId;
 
 @Column(name="childName")
 private String childName;

 @ManyToOne
 @JoinColumn(name="ParentId")
 private MappedByParent parent;
 
 public Long getPrimaryKey() {
  return getChildId();
 }

 /**
  * @return the childId
  */
 public Long getChildId() {
  return childId;
 }

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

 /**
  * @return the childName
  */
 public String getChildName() {
  return childName;
 }

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

 /**
  * @return the parent
  */
 public MappedByParent getParent() {
  return parent;
 }

 /**
  * @param parent the parent to set
  */
 public void setParent(MappedByParent parent) {
  this.parent = parent;
 }

}



MappedDAOImpl.java which extends from BaseDAOImpl and perform basic operations like persist,update,delete,findById and others for the domain object
package com.kunaal.dao.impl;

import com.kunaal.dao.IMappedDAO;
import com.kunaal.model.mappedBy.MappedByParent;

/**
 * @author Kunaal A Trehan
 *
 */
public class MappedDAOImpl extends BaseDAOImpl<MappedByParent, Long> implements IMappedDAO{

 @Override
 public Class<MappedByParent> getEntityType() {
  return MappedByParent.class;
 }

}


Abstract BaseDAOImpl.java containing all basic operations
package com.kunaal.dao.impl;

import java.io.Serializable;
import java.util.List;

import org.hibernate.SessionFactory;
import org.hibernate.classic.Session;

import com.kunaal.dao.IBaseDAO;
import com.kunaal.model.BaseModel;

/**
 * @author Kunaal A Trehan
 *
 */
public abstract class BaseDAOImpl<K extends BaseModel<T>,T> implements IBaseDAO<K , T>{

 private SessionFactory sessionFactory;
 
 public abstract Class<K> getEntityType();
 
 public void persist(K object) {
  Session session = sessionFactory.getCurrentSession();
  session.save(object);
  
 }

 public void update(K object) {
  Session session = sessionFactory.getCurrentSession();
  session.update(object);
 }

 public void delete(K object) {
  Session session = sessionFactory.getCurrentSession();
  session.delete(object);
 }

 public List<K> listAll() {
  Session session = sessionFactory.getCurrentSession();
  return session.createCriteria(getEntityType()).list();
 }

 public K findById(T id) {
  Session session = sessionFactory.getCurrentSession();
  return (K) session.get(getEntityType(), (Serializable) id);
 }

 /**
  * @return the sessionFactory
  */
 public SessionFactory getSessionFactory() {
  return sessionFactory;
 }

 /**
  * @param sessionFactory the sessionFactory to set
  */
 public void setSessionFactory(SessionFactory sessionFactory) {
  this.sessionFactory = sessionFactory;
 }

}



Abstract BaseServiceImpl.java containing all the common methods like persist,delete,update and others
package com.kunaal.service.impl;

import java.util.List;

import com.kunaal.dao.IBaseDAO;
import com.kunaal.model.BaseModel;
import com.kunaal.service.IBaseService;

/**
 * @author Kunaal A Trehan
 *
 */
public abstract class BaseServiceImpl<K extends BaseModel<T>,T> implements 
       IBaseService<K , T>{

 private IBaseDAO<K,T> baseDAO;
 
 public void persist(K object) {
  getBaseDAO().persist(object);
 }

 public void update(K object) {
  getBaseDAO().update(object);
 }

 public void delete(K object) {
  getBaseDAO().delete(object);
 }

 public List<K> listAll() {
  return getBaseDAO().listAll();
 }

 public K findById(T id) {
  return getBaseDAO().findById(id);
 }

 /**
  * @return the baseDAO
  */
 public IBaseDAO<K, T> getBaseDAO() {
  return baseDAO;
 }

 /**
  * @param baseDAO the baseDAO to set
  */
 public void setBaseDAO(IBaseDAO<K, T> baseDAO) {
  this.baseDAO = baseDAO;
 }

}


MappedServiceImpl .java extending from BaseServiceImpl
package com.kunaal.service.impl;

import com.kunaal.model.mappedBy.MappedByParent;
import com.kunaal.service.IMappedService;

/**
 * @author Kunaal A Trehan
 *
 */
public class MappedServiceImpl extends BaseServiceImpl<MappedByParent, Long> implements IMappedService{

}



MappedServiceTest.java

In this test class we have two methods
-insertProperInfo
-insertImProperInfo

In insertProperInfo() ,child entity is attaching itself to parent and thus forming the relationship.
In insertImProperInfo(),child entity is not associating itself to parent.So no relationship is formed.As a result of which foreign key is passed as NULL in the database.


package com.kunaal.service;

import java.util.HashSet;
import java.util.Set;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.transaction.TransactionConfiguration;
import org.springframework.transaction.annotation.Transactional;

import com.kunaal.model.mappedBy.MappedByChild;
import com.kunaal.model.mappedBy.MappedByParent;

/**
 * @author Kunaal A Trehan
 *
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"classpath:spring-main.xml"})
@TransactionConfiguration(defaultRollback=false)
public class MappedServiceTest implements ApplicationContextAware{

 private ApplicationContext appCtx;
 
 @Autowired
 private IMappedService mappedService;
 
 public void setApplicationContext(ApplicationContext applicationContext)
   throws BeansException {
  this.appCtx=applicationContext;
 }

 /**
  * In this test case all information and relationships are made proper
  */
 @Test
 @Transactional
 public void insertProperInfo(){
  MappedByParent parent=new MappedByParent();
  MappedByChild child1=new MappedByChild();
  MappedByChild child2=new MappedByChild();
  
  parent.setAddress("Bangalore");
  parent.setName("Parent1");
  
  child1.setChildName("Child1");
  child2.setChildName("child2");
  
  //make associations
  child1.setParent(parent);
  child2.setParent(parent);
  Set<MappedByChild> childSet=new HashSet<MappedByChild>();
  childSet.add(child1);
  childSet.add(child2);
  parent.setChildSet(childSet);
  
  mappedService.persist(parent);
 }
 
 @Test
 @Transactional
 public void insertImProperInfo(){
  MappedByParent parent=new MappedByParent();
  MappedByChild child1=new MappedByChild();
  MappedByChild child2=new MappedByChild();
  
  parent.setAddress("Bangalore");
  parent.setName("Parent2");
  
  child1.setChildName("ImproperChild1");
  child2.setChildName("ImproperChild2");
  
  //make associations
  //HERE I AM NOT PUTTING THE RELATIONSHIP ASSOCIATION SO 
  //RELATIONSHIPS ARE NOT SET.THERE WILL BE NULL IN THE FOREIGN KEY [ParentId]
  //child1.setParent(parent);
  //child2.setParent(parent);
  Set<MappedByChild> childSet=new HashSet<MappedByChild>();
  childSet.add(child1);
  childSet.add(child2);
  parent.setChildSet(childSet);
  
  mappedService.persist(parent);
 }
}


SQL Server screen showing the information after the test case execution.