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
| 22840 views |
  • submit to reddit

In this article we will see how Hibernate provides built-in types that map to common database types. We'll also see how Hibernate allows us to implement and use custom types when these built-in types do not satisfy the application's requirements, or when we want to change the default behavior of a built-in type. As you will see, you can easily implement a custom-type class and then use it in the same way as a built-in one.

Hibernate allows transparent persistence, which means the application is absolutely isolated from the underlying database storage format. Three players in the Hibernate scene implement this feature: Hibernate dialect, Hibernate types, and HQL. The Hibernate dialect allows us to use a range of different databases, supporting different, proprietary variants of SQL and column types. In addition, HQL allows us to query persisted objects, regardless of their relational persisted form in the database.

Hibernate types are a representation of databases SQL types, provide an abstraction of the underlying database types, and prevent the application from getting involved with the actual database column types. They allow us to develop the application without worrying about the target database and the column types that the database supports. Instead, we get involved with mapping Java types to Hibernate types. The database dialect, as part of Hibernate, is responsible for transforming Java types to SQL types, based on the target database. This gives us the flexibility to change the database to one that may support different column types or SQL without changing the application code.

Built-in types

Hibernate includes a rich and powerful range of built-in types. These types satisfy most needs of a typical application, providing a bridge between basic Java types and common SQL types. Java types mapped with these types range from basic, simple types, such as long and int, to large and complex types, such as Blob and Clob. The following table categorizes Hibernate built-in types with corresponding Java and SQL types:

 

Although the SQL types specified in the table above are standard SQL types, your database may support somewhat different SQL types. Refer to your database documentation to find out which types you may use instead of the standard SQL types shown in the table above.

Don't worry about the SQL types that your database supports. The SQL dialect and JDBC driver are always responsible for transforming the Java type values to appropriate SQL type representations.

The type attribute specifies Hibernate types in mapping definitions. This helps Hibernate to create an appropriate SQL statement when the class property is stored, updated, or retrieved from its respective column.

The type attribute may appear in different places in a mapping file. You may use it with the <id>, <property>, <discriminator>, <index>, and <element> elements. Here is a sample mapping file with some type attributes in different locations:

<hibernate-mapping>
<class name="Person" table="PERSON" discriminator-value="PE">

<id name="id" column="ID" type="long">
<generator class="native"/>
</id>

<discriminator column="PERSON_TYPE" type="string"/>

<property name="birthdate" column="BIRTHDATE" type="date"/>

<list name="papers" table="STUDENT_PAPER">
<key column="STUDENT_ID"/>
<list-index column="POSITION"/>
<element column="PAPER_PATH" type="string"/>
</list>

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

 

If a property is mapped without the type attribute, Hibernate uses the reflection API to find the actual type of that property and uses the corresponding Hibernate type for it. However, you should specify the type attribute if that property can be mapped with more than one Hibernate type. For example, if a property is of type java.lang.String, and its mapping definition does not include the type attribute, Hibernate will use the reflection API and select the type string for it. This means you need to explicitly define the Hibernate type for a Java String if you want to map the String with a character or text Hibernate type.

Custom types

For most mappings, Hibernate's built-in types are enough. However, in some situations, you may need to define a custom type. These situations generally happen when we want Hibernate to treat basic Java types or persistent classes differently than it normally would. Here are some situations where you may need to define and use a custom type:

  • Storing a particular Java type in a column with a different SQL type than Hibernate normally uses: For example, you might want to store a java.util.Date object in a column of type VARCHAR, or a String object in a DATE column.
  • Mapping a value type: Value types, the dependent persistent classes that do not have their own identifiers, can be mapped with custom types. This means you can treat value types similarly to primitive types and map them with the <property> element, instead of <component>.
  • Splitting up a single property value and storing the result in more than one database column: For example, assume that any phone number is split-up into four components—representing country code, area code, exchange, and line number, stored in four columns of the database. We may take this approach to provide a search facility for countries, areas, exchanges, and line numbers. If the phone numbers are represented as long numbers populated from four columns, we need to define a custom type and tell Hibernate how to assemble the number.
  • Storing more than one property in a single column: For example, if a papersproperty of a Student class is represented as an object of java.util.List and holds the file paths of all of the papers the student has written. You can define a custom type to persist all of the papers file paths as a semicolon-separated string in a single column.
  • Using an application-specific class as an identifier for the persistent class: For example, suppose you want to use the application-specific class CustomIdentifier, instead of the int, long, String, and so on, for persistent class identifiers. In this case, you also need to implement an IdentifierGenerator to tell Hibernate how to create new identifier values for non-persisted objects.

In practice, other use cases also need custom types for implementation and use. In all of these situations, you must tell Hibernate how to map a particular Java type to a database representation. You do this by implementing one of the interfaces which Hibernate provides for this purpose. The basic and most commonly used of these interfaces include org.hibernate.usertype.UserType and org.hibernate. usertype.CompositeUserType. Let's look at these in detail, discussing their differences, and how to use them.

UserType

UserType is the most commonly used Hibernate extension type. This interface exposes basic methods for defining a custom type. Here, we introduce a simple case and show how a custom type can provide a convenient mapping definition for it.

Suppose that the history of any school is represented by an individual class, History. Obviously, the History class is a value type, because no other persistent class uses the History class for its own use. This means that all History objects depend on School objects. Moreover, each school has its own history, and history is never shared between schools. Here is the School class:

package com.packtpub.springhibernate.ch07;

import java.io.Serializable;

public class School implements Serializable {

private long id;
private History history ;
//other fields

//setter and getter methods
public long getId() {
return id;
}

public void setId(long id) {
this.id = id;
}

public History getHistory() {
return history;
}

public void setHistory(History history) {
this.history = history;
}

//other setters and getters
}

 

And this is the History class:

package com.packtpub.springhibernate.ch07;

import java.io.Serializable;
import java.util.Date;

public class History implements Serializable {
long initialCapacity;
Date establishmentDate;

public long getInitialCapacity() {
return initialCapacity;
}

public void setInitialCapacity(long initialCapacity) {
this.initialCapacity = initialCapacity;
}

public Date getEstablishmentDate() {
return establishmentDate;
}

public void setEstablishmentDate(Date establishmentDate) {
this.establishmentDate = establishmentDate;
}
}

Note that I have intentionally omitted all irrelevant fields of the two classes to keep the example simple.

Our strategy in mapping a value type so far is to use one table for persisting both the persistent class and its associated value types. Based on this strategy, we need to use a SCHOOL table, which stores all of the School and History properties, and then map both School and its History class into that table through the <component> element in the mapping file. The mapping definition for School and its associated History class is as follows:

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

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

 

As an alternative approach, you can map the History class with a custom type. You do this by implementing a custom type, HistoryType, which defines how to map History objects to the target table. Actually, Hibernate does not persist a custom type. Instead, the custom type gives Hibernate information about how to persist a value type in the database. Let's implement a basic custom type by implementing the UserType interface. In the next section of this article, we'll discuss how to map History with an implementation of another Hibernate custom type interface, CompositeUserType.

The following code shows the HistoryType class that implements the UserType interface, providing a custom type for the History class:

package com.packtpub.springhibernate.ch07;

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

import org.hibernate.Hibernate;
import org.hibernate.HibernateException;
import org.hibernate.usertype.UserType;

public class HistoryType implements UserType {

private int[] types = { Types.BIGINT, Types.DATE};

public int[] sqlTypes() {
return types;
}

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

public boolean equals(Object a, Object b) throws HibernateException {
return (a == b) ||
((a != null) && (b != null) && (a.equals(b)));
}

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

public Object nullSafeGet(ResultSet rs, String[] names, Object owner)
throws HibernateException, SQLException {
Long initialCapacity = rs.getLong(names[0]);
// check if the last column read is null
if (rs.wasNull()) return null;
Date establishmentDate = rs.getDate(names[1]);
History history = new History() ;
history.setInitialCapacity(initialCapacity.longValue());
history.setEstablishmentDate(establishmentDate);
return history;
}

public void nullSafeSet(PreparedStatement ps, Object value, int index)
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 initialCapacity = history.getInitialCapacity();
Date establishmentDate = history.getEstablishmentDate();
Hibernate.LONG.nullSafeSet(ps, new Long(initialCapacity), index);
Hibernate.DATE.nullSafeSet(ps, establishmentDate, index + 1);
}
}

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

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

public boolean isMutable() {
return true;
}

public Serializable disassemble(Object value) throws
HibernateException {
return (Serializable) value;
}

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

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

 

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.)