SQL Zone is brought to you in partnership with:

ahmad has posted 2 posts at DZone. View Full User Profile

An Inside Look at Hibernate Types

08.13.2010
| 24130 views |
  • submit to reddit

The following table provides a short description for the methods in the UserType interface:

Method

Description

public int[] sqlTypes()

This method returns an array of int, telling Hibernate which SQL column types to use for persisting the entity properties. You may use the SQL types defined as constants in the java.sql.Types class directly, or you may call the sqlType() method of Hibernate types defined as constants in the org.hibernate.Hibernate class. For example, in HistoryType, recently discussed, you may alternatively define the types as follows

private int[] types = {Hibernate.BIG_INTEGER.sqlType(),Hibernate.DATE.sqlType()};

Note that you should specify the SQL types in the order in which they appear in the subsequent methods.

public Class returnedClass()

This method specifies which Java value type is mapped by this custom type.

public boolean isMutable()

This method specifies whether the value type is mutable. Since immutable objects cannot be updated or deleted by the application, defining the value type as immutable allows Hibernate to do some minor performance optimization.

public Object deepCopy(Object value)

This method creates a copy of the value type if the value type is mutable; otherwise, it returns the current instance. Note that when you create a copy of an object, you should also copy the object associations and collections.

public Serializable disassemble(Object value)

Hibernate may cache any value-type instance in its second-level cache. For this purpose, Hibernate calls this method to convert the value-type instance to the serialized binary form. Return the current instance if the value type implements the java.io.Serializable interface; otherwise, convert it to a Serializable object.

public Object assemble(Serializable cached, Object owner)

Hibernate calls this method when the instance is fetched from the second-level cache and converted back from binary serialized to the object form.

public Object replace(Object original, Object target, Object owner)

Assume that the application maintains an instance of the value type that its associated session has already closed. Such objects are not tracked and managed by Hibernate, so they are called detached. Hibernate lets you merge the detached object with a session-managed persistent object through the session's merge() method. Hibernate calls the replace() method when two instances, detached and session-managed, are merged. The first and second arguments of this method are value-type instances associated with a detached and session-managed persistent object, respectively. The third argument represents the owner object, the persistent object that owns the original value type: School in our case. This method replaces the existing (target) value in the persistent object we are merging with a new (original) value from the detached persistent object we are merging. For immutable objects or null values, return the first argument. For mutable objects, return at least a copy of the first argument through the deepCopy() method.

public Object nullSafeGet(ResultSet resultSet, String[] names, Object owner)

This method constructs the value-type instance when the instance is retrieved from the database. resultset is the JDBC ResultSet object containing the instance values, names is an array of the column names queried, and owner is the persistent object associated with the value-type instance. Note that you should handle the possibility of null values.

public void nullSafeSet(PreparedStatement statement, Object value, int index)

This method is called when the value-type instance is written to a prepared statement to be stored or updated in the database. Handle the possibility of null values. A multi-column type should be written to parameters starting from index.

public boolean equals(Object x, Object y)

This method compares two instances of the value type mapped by this custom type to check whether they are equal.

public int hashCode(Object x)

This method returns a hashcode for the instance, consistent with persistence equality.

In some of the methods shown in the table above, the owner object is passed as an argument to the method. You can use this object if you need the other properties of the value-type instance. For example, you can access a property of the owner if you need it to calculate the value for a value-type property.

Serializing and caching issue
If the value type, in our case History, does not implement the java.io.Serializable interface, then its respective custom type is responsible for properly serializing or deserializing the value type. Otherwise, the value-type instances cannot be cached by the Hibernate second-level cache service.

To use the defined custom type, you need to edit the mapping file as shown below:

<hibernate-mapping>
<class name="com.packtpub.springhibernate.ch07.School" table="SCHOOL">
<id name="id" type="long" column="id">
<generator class="increment"/>
</id>
<property name="history"
type="com.packtpub.springhibernate.ch07.HistoryType">
<column="INITIAL_CAPACITY" type="long"/>
<column="ESTABLISHMENT_DATE" type="date"/>
</property>

<!-- mapping of other fields -->
</class>
</hibernate-mapping>

 

Note that you should specify the columns in order, corresponding to the order of types returned by the getTypes() method and the index of the values the nullSafeGet() and nullSafeSet() handle.

So far, all we have done is implemented a custom type in the simplest form. The implemented custom type only transforms the value-type instances to the database columns and vice versa. A custom type may be more complicated than we have seen so far, and can do much more sophisticated things. The advantage of this implemented custom type is obvious: we can define our own strategy for mapping value types. For instance, a property of the value type can be stored in more than one column, or more than one property can be stored in a single column.

The main shortcoming of this approach is that, we have hidden the value-type properties from Hibernate. Therefore, Hibernate does not know anything about the properties inside the value type, or how to query persistent objects based on their associated value types as problem constraints are involved. Let's look at CompositeUserType and how it can solve this problem.

CompositeUserType

Another way to define a custom type is to use the CompositeUserType interface. This type is similar to UserType, but with more methods to expose the internals of your value-type class to Hibernate. CompositeUserType is useful when application query expressions include constraints on value-type properties. If you want to query the persistent objects with constraints on their associated value types, map the associated value types with CompositeUserType. The following code shows the CompositeHistoryType implementation for History:

package com.packtpub.springhibernate.ch07;

import org.hibernate.usertype.CompositeUserType;
import org.hibernate.type.Type;
import org.hibernate.HibernateException;
import org.hibernate.Hibernate;
import org.hibernate.engine.SessionImplementor;

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

public class CompositeHistoryType implements CompositeUserType {

private String[] propertyNames = {"initialCapacity", "establishmentDate"};

private Type[] propertyTypes = {Hibernate.LONG, Hibernate.DATE};

public String[] getPropertyNames() {
return propertyNames;
}

public Type[] getPropertyTypes() {
return propertyTypes;
}

public Object getPropertyValue(Object component, int property) {
History history = (History) component;
switch (property) {
case 0:
return new Long(history.getInitialCapacity());
case 1:
return history.getEstablishmentDate();
}
throw new IllegalArgumentException(property +
" is an invalid property index for class type " +
component.getClass().getName());
}

public void setPropertyValue(Object component, int property,
Object value) {
History history = (History) component;
switch (property) {
case 0:
history.setInitialCapacity(((Long) value).longValue());
case 1:
history.setEstablishmentDate((Date) value);
default:
throw new IllegalArgumentException(property +
" is an invalid property index for class type " +
component.getClass().getName());
}

}

public Class returnedClass() {
return History.class;
}

public boolean equals(Object o1, Object o2) throws HibernateException {
if (o1 == o2) return true;
if (o1 == null || o2 == null) return false;
return o1.equals(o2);
}

public int hashCode(Object o) throws HibernateException {
return o.hashCode();
}

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

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

public Serializable disassemble(Object value,
SessionImplementor session)
throws HibernateException {
return (Serializable) deepCopy(value);
}

public Object nullSafeGet(ResultSet rs, String[] names,
SessionImplementor session, Object o)
throws HibernateException, SQLException {
long initialCapacity = rs.getLong(names[0]);
java.util.Date establishmentDate = rs.getDate(names[1]);
return new History(initialCapacity, establishmentDate);
}

public void nullSafeSet(PreparedStatement ps,
Object value, int index, SessionImplementor session)
throws HibernateException, SQLException {
if (value == null) {
ps.setNull(index, Hibernate.LONG.sqlType());
ps.setNull(index + 1, Hibernate.DATE.sqlType());
} else {
History history = (History) value;
long l = history.getEstablishmentDate().getTime();
ps.setLong(index, history.getInitialCapacity());
ps.setDate(index + 1, new java.sql.Date(l));
}
}

public Object deepCopy(Object value) throws HibernateException {
if (value == null) return null;
History origHistory = (History) value;
History newHistory = new History();

newHistory.setInitialCapacity(origHistory.getInitialCapacity());
newHistory.setEstablishmentDate(origHistory.getEstablishmentDate());
return newHistory;
}

public boolean isMutable() {
return true;
}
}

As you can see, this interface exposes some extra methods not seen in UserType. The following table shows the functionality of these methods:

Method

Description

public String[] getPropertyNames()

This method returns the names of the value type's properties that may appear in the query constraints. In the example shown in the code above, we have used both the initialCapacity and establishmentDate properties, meaning the application can query the persistent objects based on these property values.

public Type[] getPropertyTypes()

This method returns the corresponding types of the properties specified by the getPropertyNames() method. Each returned Type in the array corresponds to a property name with the same index in the array returned by getPropertyNames(). Each type is expressed as an instance of the org.hibernate.type.Type interface, defined as a static member in the org.hibernate.Hibernate class, or a custom type implemented by the developer.

public Object getPropertyValue(Object component, int property)

This method returns a property's value. It takes two arguments. The first argument is the value-type instance that holds the property value we want to fetch. The second argument specifies the index of the property, based on the property name returned by getPropertyNames().

public void setPropertyValue(Object component, int property, Object value)

Hibernate uses this method to assign a value to any property of the value-type instance. This method takes three arguments. The first argument refers to the value-type instance, the second specifies the index of the property based on position of the property in the array returned by getPropertyNames(), and the third is the value assigned to the property.

Using this custom type is same as using UserType, except that you need to specify the CompositeHistoryType instead of HistoryType as follows:

<property name="history" 
type="com.packtpub.springhibernate.ch07.CompositeHistoryType">
<column="INITIAL_CAPACITY" type="long"/>
<column="ESTABLISHMENT_DATE" type="date"/>
</property>

 

As mentioned earlier, this custom type provides an ability to query on properties of the History type. HQL is one approach provided by Hibernate to query the persistent object. For instance, suppose we are interested in schools established before 1980. The following code shows querying these objects with HQL, a Hibernate-specific query language that works with objects:

Calendar c = Calendar.getInstance();
c.set(Calendar.YEAR, 1980);
Query q = session.createQuery(
"select s from School s where s.history.establishmentDate < :edate"
).setParameter("edate", new Date(c.getTimeInMillis()));

 

All we have done in this snippet is created a Query object with an HQL expression indicating all School objects with establishment date before 1980. Note that HistoryCompositeType provides the ability to query the School object with criteria applied to History objects.

The only advantage of CompositeUserType over UserType is that CompositeUserType exposes the value-type properties for Hibernate. Therefore, it lets you query persistent instances based on values of their associated value-type instances.

Summary

In this article, we discussed Hibernate types, which define the mapping of each Java type to an SQL type. It is the responsibility of the Hibernate dialect and the JDBC driver to convert the Java types to the actual target SQL types. This means a Java type may be transformed to different SQL types when different databases are used.

Although Hibernate provides a rich set of data types, called built-in types, some situations require the definition of a new type. One such situation occurs when you want to change Hibernate's default behavior for mapping a Java type to an SQL type. Another situation is when you want to split up a class property to a set of table columns, or merge a set of properties to a table column.

Built-in types include primitive, string, byte array, time, localization, serializable, and JDBC large types.

Hibernate provides several interfaces for implementation by custom types. The most commonly used interfaces are org.hibernate.usertype.UserType and org.hibernate.usertype.CompositeUserType. The basic extension point is UserType. It allows us to map a value-type, but hides the value-type properties from Hibernate, so it does not provide the application with the ability to query value types. In contrast, CompositeUserType exposes the value-type properties to Hibernate, and allows Hibernate to query the value-types.

 

 This article was excerpted from the book, Spring Persistence with Hibernate (Packt Publishing, Nov 2009). 

AttachmentSize
HibernateTypes_Figure1.png48.09 KB
HibernateTypes_Figure2.png47.27 KB
Published at DZone with permission of its author, ahmad seddighi.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)