Two new classes are needed. The first is the class you want to use for the column(columns for composite user type). In my case, I created a class Phone to store data in database. Here's my class:
The factory class is produced by implementing (in the complex case) org.hibernate.usertype.CompositeUserType, you can also see simple case example. Documentation in this interface is pretty thin, but there are good examples available in the Hibernate distribution. Here's my implementation.
The core of this class is the two methods which get and set values associated with my new type: nullSafeSet and nullSafeGet. One key thing to note is that nullSafeGet is supplied with a list of all the column names mapped to the custom datatype in the current query. In my case, there's only one, but in complex cases, you can map multiple columns to one object (there are examples in the Hibernate documentation). The final piece of the puzzle is the annotation which tells Hibernate to use the new "Type" class to generate objects of your custom type by adding a new @Type annotation to the column. There would create two columns in the database as it is composite user type, not simple one. Data saved in database as follows:
package com.pkm.utils; public class Phone implements java.io.Serializable { private Integer areaCode = null; public Integer getAreaCode() { return areaCode; } public void setAreaCode(Integer areaCode) { this.areaCode = areaCode; } public void clearAreaCode() { this.areaCode = null; } private String phoneNum = null; public String getPhoneNum() { return phoneNum; } public void setPhoneNum(String phoneNum) { this.phoneNum = phoneNum; } public Phone() { } public Phone(String phoneNum, Integer areaCode) { this.phoneNum = phoneNum; this.areaCode = areaCode; } public Phone(String phoneNum) { this.phoneNum = phoneNum; this.areaCode = null; } @Override public String toString(){ return areaCode + "-" + phoneNum; } @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; } @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; } }
The factory class is produced by implementing (in the complex case) org.hibernate.usertype.CompositeUserType, you can also see simple case example. Documentation in this interface is pretty thin, but there are good examples available in the Hibernate distribution. Here's my implementation.
PhoneCompositeType.java
package com.pkm.utils; import java.io.Serializable; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import org.hibernate.HibernateException; import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.type.Type; import org.hibernate.usertype.CompositeUserType; public class PhoneCompositeType implements CompositeUserType { @Override public String[] getPropertyNames() { return new String[] {"phoneNum", "areaCode"}; } @Override public Type[] getPropertyTypes() { return new Type[]{ org.hibernate.type.StandardBasicTypes.STRING, org.hibernate.type.StandardBasicTypes.INTEGER }; } @Override public Object getPropertyValue(Object o, int i) throws HibernateException { if(o == null) { return null; } else if(i == 0) { return ((Phone) o).getPhoneNum(); } else if(i == 1) { return ((Phone) o).getAreaCode(); } return null; } @Override public void setPropertyValue(Object o, int i, Object o1) throws HibernateException { if(o != null) { if(i == 0) { ((Phone) o).setPhoneNum((String) o1); } else if(i == 1 && o1 != null) { ((Phone) o).setAreaCode((Integer) o1); } } } @Override public Class returnedClass() { return Phone.class; } @Override public boolean equals(Object o, Object o1) throws HibernateException { if(o == null && o1 == null) { return true; } if(o == null || o1 == null) { return false; } return o.equals(o1); } @Override public int hashCode(Object o) throws HibernateException { if(o != null) { return o.hashCode(); } return 0; } @Override public Object nullSafeGet(ResultSet rs, String[] strings, SessionImplementor si, Object o) throws HibernateException, SQLException { String phoneNum = rs.getString(strings[0]); String areaCode = rs.getString(strings[1]); if(phoneNum != null) { Phone phone = new Phone(phoneNum); if(areaCode != null) { phone.setAreaCode(Integer.parseInt(areaCode)); } return phone; } return null; } @Override public void nullSafeSet(PreparedStatement ps, Object o, int i, SessionImplementor si) throws HibernateException, SQLException { if(o != null) { ps.setString(i, ((Phone) o).getPhoneNum()); if(((Phone) o).getAreaCode() != null) { ps.setInt(i + 1, ((Phone) o).getAreaCode()); } else { ps.setString(i + 1, null); } } else { ps.setString(i, null); ps.setString(i + 1, null); } } @Override public Object deepCopy(Object o) throws HibernateException { if(o != null) { Phone newPhone = (Phone) o; return new Phone(newPhone.getPhoneNum(), newPhone.getAreaCode()); } return null; } @Override public boolean isMutable() { return false; } @Override public Serializable disassemble(Object o, SessionImplementor si) throws HibernateException { Object deepCopy = deepCopy(o); if(!(deepCopy instanceof Serializable)) { return (Serializable) deepCopy; } return null; } @Override public Object assemble(Serializable srlzbl, SessionImplementor si, Object o) throws HibernateException { return deepCopy(srlzbl); } @Override public Object replace(Object o, Object o1, SessionImplementor si, Object o2) throws HibernateException { return deepCopy(o); } }
The core of this class is the two methods which get and set values associated with my new type: nullSafeSet and nullSafeGet. One key thing to note is that nullSafeGet is supplied with a list of all the column names mapped to the custom datatype in the current query. In my case, there's only one, but in complex cases, you can map multiple columns to one object (there are examples in the Hibernate documentation). The final piece of the puzzle is the annotation which tells Hibernate to use the new "Type" class to generate objects of your custom type by adding a new @Type annotation to the column. There would create two columns in the database as it is composite user type, not simple one. Data saved in database as follows:
Use new type as follows:
@Type(type = "com.pkm.utils.PhoneCompositeType") @Columns(columns = { @Column(name = "phone_number"), @Column(name = "area_code", nullable = true) }) private Phone phone = new Phone(); public void setPhone(Phone phone) { this.phone = phone; } public Phone getPhone() { if(phone == null) { phone = new Phone(); } return phone; }
Use as follows
User user = (User) session.get(User.class, 1); user.getPhone().setAreaCode(880); user.getPhone().setPhoneNum("01727499452");
Output in my database as like (After save/update reading values from database):
Phone area: 880 Phone number: 01727499452