Let us look at the EnhancedUserType.This interface extends the UserType interface and provides additional methods to allow usage in XML representation. I shall implement a type that will allow saving of Enums directly to the database.
Before this if we needed to map a column in the database table with an enum, we might have saved a string or integer representation of the enum to the database and written code to manually convert the SQL value into an enum entry and vice verse.
Consider our Enum and the Java Model Class :
The skill property has been specified with a new customized type. The code for this type is as below:
On executing code to create a row of User in the table
The query fired does a varchar based comparism in the database to return the actual record
Before this if we needed to map a column in the database table with an enum, we might have saved a string or integer representation of the enum to the database and written code to manually convert the SQL value into an enum entry and vice verse.
Consider our Enum and the Java Model Class :
public class SkilledUser { private Long id; private String name; private Skill skill; public Skill getSkill() { return skill; } public void setSkill(Skill skill) { this.skill = skill; } //Other setter getter methods }
package com.model.enumtype; public enum Skill { GOOD("good"), BETTER("better"), BEST("best"); private String id; private Skill(final String id) { this.id = id; } public String getId() { return id; } public static Skill getSkill(String id) { Skill targetSkill = null; for (Skill skill : Skill.values()) { if (skill.getId().equals(id)) { targetSkill = skill; } } return targetSkill; } }
The hibernate mapping for the SkilledUser class is as below:
<?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.model"> <class name="SkilledUser" table="USER"> <id name="id" type="long"> <column name="ID" /> <generator class="native" /> </id> <property name="name" type="string"> <column name="NAME" /> </property> <property name="skill" type="com.customtype.EnhancedUserTypeForSkill"> <column name="SKILL" /> </property> </class> </hibernate-mapping>
The skill property has been specified with a new customized type. The code for this type is as below:
package com.customtype; import java.io.Serializable; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import org.hibernate.Hibernate; import org.hibernate.HibernateException; import org.hibernate.usertype.EnhancedUserType; import com.model.enumtype.Skill; public class EnhancedUserTypeForSkill implements EnhancedUserType { /** * This tells Hibernate to represent the column with the data-type specified here */ @Override public int[] sqlTypes() { return new int[] {Hibernate.STRING.sqlType()}; } @SuppressWarnings("rawtypes") @Override /** * method tells Hiberante that data recived from sql row should be mapped to Skill enum */ public Class returnedClass() { return Skill.class; } @Override //Used for dirty checking public boolean equals(Object o1, Object o2) throws HibernateException { boolean isEqual = false; if (o1 == o2) { isEqual = true; } if (null == o1 || null == o2) { isEqual = false; } else { isEqual = o1.equals(o2); } return isEqual; } @Override //Used for dirty checking public int hashCode(Object x) throws HibernateException { Skill skill = (Skill) x; return skill.hashCode(); } @Override public Object deepCopy(Object value) throws HibernateException { return value; //as Enum is immutable its returns the same reference } @Override public boolean isMutable() { return false;//As Enum is immutable } @Override //called when a Skill object is stored in Hibernate second level cache public Serializable disassemble(Object value) throws HibernateException { return (Serializable) value; } @Override //called when a Skill object is retrieved from Hibernate second level cache public Object assemble(Serializable cached, Object owner) throws HibernateException { return cached; //returning the same object as is simply a reference to an Enum constant } //used to merge detached objects @Override public Object replace(Object original, Object target, Object owner) throws HibernateException { return original; // if immutable use this } @Override public Object nullSafeGet(ResultSet rs, String[] names, Object owner) throws HibernateException, SQLException { Skill skill = null; String name = rs.getString(names[0]); if (null != name) {//The actual conversion from string to Enum skill = Skill.getSkill(name); } return skill; } @Override public void nullSafeSet(PreparedStatement st, Object value, int index) throws HibernateException, SQLException { if (null == value) { st.setNull(index, Hibernate.STRING.sqlType()); } else {//the conversion from Enum to string Skill skill = (Skill) value; st.setString(index, skill.getId()); } } //used for XML Marshalling @Override public String objectToSQLString(Object value) { Skill skill = (Skill) value; return '\'' + skill.name() +'\''; } //used for XML Marshalling @Override public String toXMLString(Object value) { Skill skill = (Skill) value; return skill.getId(); } //used for XML Marshalling @Override public Object fromXMLString(String xmlValue) { return Skill.valueOf(xmlValue); } }
I have added the comments in the java class itself. The start up logs are as below:
438 [main] DEBUG org.hibernate.persister.entity.AbstractEntityPersister - Stat ic SQL for entity: com.model.SkilledUser 2438 [main] DEBUG org.hibernate.persister.entity.AbstractEntityPersister - Ver sion select: select ID from USER where ID =? 2438 [main] DEBUG org.hibernate.persister.entity.AbstractEntityPersister - Sna pshot select: select skilleduse_.ID, skilleduse_.NAME as NAME0_, skilleduse_.SKI LL as SKILL0_ from USER skilleduse_ where skilleduse_.ID=? 2438 [main] DEBUG org.hibernate.persister.entity.AbstractEntityPersister - Ins ert 0: insert into USER (NAME, SKILL, ID) values (?, ?, ?) 2438 [main] DEBUG org.hibernate.persister.entity.AbstractEntityPersister - Upd ate 0: update USER set NAME=?, SKILL=? where ID=? 2438 [main] DEBUG org.hibernate.persister.entity.AbstractEntityPersister - Del ete 0: delete from USER where ID=? 2438 [main] DEBUG org.hibernate.persister.entity.AbstractEntityPersister - Ide ntity insert: insert into USER (NAME, SKILL) values (?, ?) ... 2641 [main] DEBUG org.hibernate.tool.hbm2ddl.SchemaExport - drop table if exists USER 2704 [main] DEBUG org.hibernate.tool.hbm2ddl.SchemaExport - create table USER ( ID bigint not null auto_increment, NAME varchar(255), SKILL varchar(255), primary key (ID) )
On executing code to create a row of User in the table
static void testCreate() { Session session = SESSION_FACTORY.openSession(); Transaction transaction = null; try { final SkilledUser user = new SkilledUser(); user.setName("Lowest Skilled user"); user.setSkill(Skill.GOOD); transaction = session.beginTransaction(); session.save(user); transaction.commit(); } catch (HibernateException e) { e.printStackTrace(); transaction.rollback(); } finally { session.close(); } }
4031 [main] DEBUG org.hibernate.SQL -
insert
into
USER
(NAME, SKILL)
values
(?, ?)
The record created in the table holds the string value for the skill 'Good'. I fetched the same record now created using a simple hql query
static void testFetchByQuery() { Session session = SESSION_FACTORY.openSession(); try { Query query = session .createQuery("from SkilledUser user where user.skill = com.model.enumtype.Skill.GOOD"); SkilledUser user = (SkilledUser) query.list().get(0); System.out.println("Name : " + user.getName() + " Id : " + user.getId()); } catch (HibernateException e) { e.printStackTrace(); } finally { session.close(); } }
The query fired does a varchar based comparism in the database to return the actual record
3063 [main] DEBUG org.hibernate.SQL - select skilleduse0_.ID as ID0_, skilleduse0_.NAME as NAME0_, skilleduse0_.SKILL as SKILL0_ from USER skilleduse0_ where skilleduse0_.SKILL='GOOD'
How did the above query work ? More specifically how did Hibernate convert the enum instance of GOOD to the literal string 'Good' ?
The EnhancedUserType used the objectToSQLString() method to convert the object in the HQL to a string representation. The method was invoked by the HQL parser.
Thus now we can directly use the enum constants/names in our HQL queries. Instead of hard-coding the value as in the HQL query, we could pass it as a value to the method.
Also by combining the ParametrizedType Interface with the above code it is possible to create a single generic Type class to handle all the different enum mappings in our code.
The EnhancedUserType used the objectToSQLString() method to convert the object in the HQL to a string representation. The method was invoked by the HQL parser.
Thus now we can directly use the enum constants/names in our HQL queries. Instead of hard-coding the value as in the HQL query, we could pass it as a value to the method.
Also by combining the ParametrizedType Interface with the above code it is possible to create a single generic Type class to handle all the different enum mappings in our code.
No comments:
Post a Comment