As seen in the previous blog, the UserType interface does not provide the ability to use the properties of the new Type in Hibernate Queries. For this we need to implement the more powerful CompositeUserType.
Using the same AuditData object from the previous example,
I created a new custom type that would support the querying feature.
Using the same AuditData object from the previous example,
I created a new custom type that would support the querying feature.
package com.customtype; import java.io.Serializable; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Date; import org.hibernate.Hibernate; import org.hibernate.HibernateException; import org.hibernate.engine.SessionImplementor; import org.hibernate.type.Type; import org.hibernate.usertype.CompositeUserType; import com.model.component.AuditData; public class CompositeAuditType implements CompositeUserType { /** * This returns the Type of the SQL columns */ @Override public String[] getPropertyNames() { return new String[] { "createdBy", "createdDate", "modifiedBy", "modifiedDate" }; } /** * The name of the properties are exposed here */ @Override public Type[] getPropertyTypes() { //createdBy, createdDate,modifiedBy,modifiedDate return new Type[] { Hibernate.INTEGER, Hibernate.TIMESTAMP, Hibernate.INTEGER, Hibernate.TIMESTAMP }; } /** * This will return the individual value of the property */ @Override public Object getPropertyValue(final Object component, final int property) throws HibernateException { Object returnValue = null; final AuditData auditData = (AuditData) component; if (0 == property) { returnValue = auditData.getCreatedBy(); } else if (1 == property) { returnValue = auditData.getCreatedDate(); } else if (2 == property) { returnValue = auditData.getModifiedBy(); } else if (3 == property) { returnValue = auditData.getModifiedDate(); } return returnValue; } /** * This method is called to convert the sql column data into Java model object */ @Override public void setPropertyValue(final Object component, final int property, final Object setValue) throws HibernateException { final AuditData auditData = (AuditData) component; if (0 == property) { final Integer createdBy = (Integer) setValue; auditData.setCreatedBy(createdBy); } else if (1 == property) { final Date createdDate = (Date) setValue; auditData.setCreatedDate(createdDate); } else if (2 == property) { final Integer modifiedBy = (Integer) setValue; auditData.setModifiedBy(modifiedBy); } else if (3 == property) { final Date modifiedDate = (Date) setValue; auditData.setModifiedDate(modifiedDate); } } /** * This is called so as to retrieve the values from the sql resultset */ @Override public Object nullSafeGet(final ResultSet resultSet, final String[] names, final SessionImplementor paramSessionImplementor, final Object paramObject) throws HibernateException, SQLException { //owner here is of type TestUser or the actual owning Object AuditData auditData = null; final Integer createdBy = resultSet.getInt(names[0]); //Deferred check after first read if (!resultSet.wasNull()) { auditData = new AuditData(); auditData.setCreatedBy(createdBy); final Date createdDate = resultSet.getTimestamp(names[1]); final Integer modifiedBy = resultSet.getInt(names[2]); final Date modifiedDate = resultSet.getTimestamp(names[3]); auditData.setCreatedDate(createdDate); auditData.setModifiedBy(modifiedBy); auditData.setModifiedDate(modifiedDate); } return auditData; } /** * Before executing the save call this method is called. It will set the * values in the prepared statement */ @Override public void nullSafeSet(final PreparedStatement preparedStatement, final Object value, final int property, final SessionImplementor sessionImplementor) throws HibernateException, SQLException { if (null == value) { preparedStatement.setNull(property, Hibernate.INTEGER.sqlType()); preparedStatement.setNull(property + 1, Hibernate.TIMESTAMP.sqlType()); preparedStatement.setNull(property + 2, Hibernate.INTEGER.sqlType()); preparedStatement.setNull(property + 3, Hibernate.TIMESTAMP.sqlType()); } else { final AuditData auditData = (AuditData) value; preparedStatement.setInt(property, auditData.getCreatedBy()); preparedStatement.setTimestamp(property + 1, new Timestamp(auditData.getCreatedDate().getTime())); preparedStatement.setInt(property + 2, auditData.getModifiedBy()); preparedStatement.setTimestamp(property + 3, new Timestamp(auditData.getModifiedDate().getTime())); } } /** * method called when Hibernate puts the data in a second level cache. The data is stored * in a serializable form */ @Override public Serializable disassemble(final Object value, final SessionImplementor paramSessionImplementor) throws HibernateException { //Thus the data Types must implement serializable return (Serializable) value; } /** * Returns the object from the 2 level cache */ @Override public Object assemble(final Serializable cached, final SessionImplementor sessionImplementor, final Object owner) throws HibernateException { //would work as the class is Serializable, and stored in cache as it is - see disassemble return cached; } /** * Method is called when merging two objects. */ @Override public Object replace(final Object original, final Object target, final SessionImplementor paramSessionImplementor, final Object owner) throws HibernateException { // return original; // if immutable use this //For mutable types at bare minimum return a deep copy of first argument return this.deepCopy(original); } @SuppressWarnings("rawtypes") @Override public Class returnedClass() { return AuditData.class; } /** * Used while dirty checking - control passed on to the {@link AuditData} */ @Override public boolean equals(final Object o1, final Object o2) throws HibernateException { boolean isEqual = false; if (o1 == o2) { isEqual = false; } if (null == o1 || null == o2) { isEqual = false; } else { isEqual = o1.equals(o2); } return isEqual; } @Override public int hashCode(final Object value) throws HibernateException { return value.hashCode(); } /** * Helps hibernate apply certain optimizations for immutable objects */ @Override public boolean isMutable() { return true; } /** * Used to create Snapshots of the object */ @Override public Object deepCopy(final Object value) throws HibernateException { // return value; if object was immutable we could return the object as its is final AuditData recievedParam = (AuditData) value; final AuditData auditData = new AuditData(recievedParam); return auditData; } }
As can be seen in the above code additional methods are available with this interface.
- The getPropertyNames() returns the properties present in this new type.
- The getPropertyTypes() indicates the type of each property.
- The getPropertyValue() and setPropertyValue() methods allows for access and modification of these individual properties.
<?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="TypedUser" 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 = "auditData" type ="com.customtype.AuditType"> --> <property name = "auditData" type ="com.customtype.CompositeAuditType"> <column name="Created_By" /> <column name="Created_Date" length="19" /> <column name="Modified_By" /> <column name="Modified_Date" length="19" /> </property> </class> </hibernate-mapping>
On start up the logs(cleaned) are as below:
1031 [main] INFO org.hibernate.cfg.HbmBinder - Mapping class: com.model.TypedU ser -> USER 1047 [main] DEBUG org.hibernate.cfg.HbmBinder - Mapped property: id -> ID 1063 [main] DEBUG org.hibernate.cfg.HbmBinder - Mapped property: name -> NAME 1063 [main] DEBUG org.hibernate.cfg.HbmBinder - Mapped property: auditData -> C reated_By, Created_Date, Modified_By, Modified_Date
On executing an hql query to retrieve records with created_By value as 1:
static void testFetchByQuery() { Session session = SESSION_FACTORY.openSession(); try { Query query = session.createQuery("from TypedUser user where user.auditData.createdBy = 1"); TypedUser user = (TypedUser) query.uniqueResult(); System.out.println("Name : " + user.getName() + " Audit details : " + user.getAuditData() ); } catch (HibernateException e) { e.printStackTrace(); } finally { session.close(); } }
Logs:
6828 [main] DEBUG org.hibernate.engine.query.QueryPlanCache - unable to locate HQL query plan in cache; generating (from TypedUser user where user.auditData.cr eatedBy = 1) 7031 [main] DEBUG org.hibernate.hql.ast.QueryTranslatorImpl - parse() - HQL: fr om com.model.TypedUser user where user.auditData.createdBy = 1 7094 [main] DEBUG org.hibernate.hql.ast.AST - --- HQL AST --- \-[QUERY] 'query' +-[SELECT_FROM] 'SELECT_FROM' | \-[FROM] 'from' | \-[RANGE] 'RANGE' | +-[DOT] '.' | | +-[DOT] '.' | | | +-[IDENT] 'com' | | | \-[IDENT] 'model' | | \-[IDENT] 'TypedUser' | \-[ALIAS] 'user' \-[WHERE] 'where' \-[EQ] '=' +-[DOT] '.' | +-[DOT] '.' | | +-[IDENT] 'user' | | \-[IDENT] 'auditData' | \-[IDENT] 'createdBy' \-[NUM_INT] '1' 7281 [main] DEBUG org.hibernate.hql.ast.tree.FromElement - handling property de reference [com.model.TypedUser (user) -> auditData (class)] 7281 [main] DEBUG org.hibernate.hql.ast.tree.DotNode - getDataType() : auditDat a -> org.hibernate.type.CompositeCustomType@1388e5e 7281 [main] DEBUG org.hibernate.hql.ast.tree.DotNode - Unresolved property path is now 'auditData.createdBy' 7281 [main] DEBUG org.hibernate.hql.ast.tree.FromReferenceNode - Resolved : us er.auditData -> typeduser0_.Created_By 7297 [main] DEBUG org.hibernate.hql.ast.tree.DotNode - getDataType() : auditDat a.createdBy -> org.hibernate.type.IntegerType@ad483 7297 [main] DEBUG org.hibernate.hql.ast.tree.FromReferenceNode - Resolved : us er.auditData.createdBy -> typeduser0_.Created_By ... 7359 [main] DEBUG org.hibernate.hql.ast.QueryTranslatorImpl - SQL: select typed user0_.ID as ID0_, typeduser0_.NAME as NAME0_, typeduser0_.Created_By as Created 3_0_, typeduser0_.Created_Date as Created4_0_, typeduser0_.Modified_By as Modifi ed5_0_, typeduser0_.Modified_Date as Modified6_0_ from USER typeduser0_ where ty peduser0_.Created_By=1 ... 7391 [main] DEBUG org.hibernate.connection.DriverManagerConnectionProvider - to tal checked-out connections: 0 7391 [main] DEBUG org.hibernate.connection.DriverManagerConnectionProvider - us ing pooled JDBC connection, pool size: 0 7391 [main] DEBUG org.hibernate.SQL - select typeduser0_.ID as ID0_, typeduser0_.NAME as NAME0_, typeduser0_.Created_By as Created3_0_, typeduser0_.Created_Date as Created4_0_, typeduser0_.Modified_By as Modified5_0_, typeduser0_.Modified_Date as Modified6_0_ from USER typeduser0_ where typeduser0_.Created_By=1 ... Name : New User Audit details : [ class com.model.component.AuditData { createdB y : 1, createdDate: 2011-08-21 12:06:51.0, modifiedBy: 1, modifiedDate: 2011-08- 21 12:06:51.0}] 7578 [main] DEBUG org.hibernate.impl.SessionImpl - closing session
As can be seen from the logs, the query parser parsed the hql query, identified the attributes and executed the generated sql query to return the result. If the same query was executed using the UserType, the exception thrown is as below:
org.hibernate.QueryException: could not resolve property: createdBy of: com.mode
l.TypedUser [from com.model.TypedUser user where user.auditData.createdBy = 1]
at org.hibernate.persister.entity.AbstractPropertyMapping.propertyException(Abs
tractPropertyMapping.java:44)
at org.hibernate.persister.entity.AbstractPropertyMapping.toType(AbstractProper
tyMapping.java:38)
at org.hibernate.persister.entity.AbstractEntityPersister.toType(AbstractEntity
Persister.java:1358)
at org.hibernate.hql.ast.tree.FromElementType.getPropertyType(FromElementType.j
ava:279)
at org.hibernate.hql.ast.tree.FromElement.getPropertyType(FromElement.java:386)
No comments:
Post a Comment