Thursday, August 27, 2009

Using TINY_INT for Java Boolean types with Hibernate

>>We are using Spring 2.0 and Hibernate 3.2 with a MySQL 5 database.

THE PROBLEM: By default Hibernate persists properties that are of Java Boolean or boolean type as CHAR type with values 0 and 1 in our MySQL database. This is efficient, but when we want to look at the database using our GUI tools, these values both display as the same meaningless box character. This is because the ASCII values 0 and 1, both represent non-printable characters.

THE SOLUTION: If we force Hibernate to make these columns TINY_INT type, our tools will display the values correctly as “0” and “1”. Hibernate allows us to do this by creating a custom type and assigning it to all of our boolean properties. Here’s how:

1. Create a custom Hibernate type. We extend Hibernate’s abstract BooleanType and override a few methods to customize it for our needs.

OneZeroBoolean.java

package com.example.model;

import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;

import org.hibernate.dialect.Dialect;
import org.hibernate.type.BooleanType;

public class OneZeroBoolean extends BooleanType
{
private static final long serialVersionUID = 1L;

public Object get(ResultSet rs, String name) throws SQLException
{
if (rs.getObject(name) == null)
return null;
int code = rs.getInt(name);
return code != 0;
}

public void set(PreparedStatement st, Object value, int index) throws SQLException
{
if (value == null)
st.setObject(index, null);
else
st.setInt(index, Boolean.TRUE.equals(value) ? 1 : 0);
}

public int sqlType()
{
return Types.TINYINT;
}

public String objectToSQLString(Object value, Dialect dialect) throws Exception
{
return ((Boolean)value).booleanValue() ? "1" : "0";
}

public Object stringToObject(String xml) throws Exception
{
if ("0".equals(xml))
{
return Boolean.FALSE;
} else
{
return Boolean.TRUE;
}
}
}

2. Register this new type with Hibernate. We are using annotations for our hibernate configuration, so this is done in the package-info.java file in the package where our entity classes are defined. Here register our new type under the name “onezero-boolean”. You can use any name you like (unless it’s already used by hibernate). We will reference this name later when we declare the boolean properties themselves.

package-info.java

@TypeDefs( { @TypeDef(name = "onezero-boolean", typeClass = OneZeroBoolean.class) })
package com.example.model;

import org.hibernate.annotations.TypeDefs;
import org.hibernate.annotations.TypeDef;

NOTE: In order to use these package-level annotations we need to tell the Hibernate configuration to look for them. Your configuration may be in hibernate.cfg.xml or you may be doing it in Spring. Here are examples from both…

Hibernate.cfg.xml: Add a mapping within <session-factory> telling hibernate to look for package-level annotations in the specified package.

hibernate.cfg.xml

<hibernate-configuration>
<session-factory>
...
<mapping package="com.example.model" />
...

Spring: Spring provides some helpers to configure a hibernate session factory as an alternative to specifying everything in hibernate.cfg.xml. In Spring 2.0 it’s the org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean class. Here is a snippet of the configuration including the extra piece needed for package annotations:

applicationContext.xml

<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">${hibernate.dialect}</prop>
<prop key="hibernate.show_sql">${hibernate.show_sql}</prop>
</props>
</property>
<!-- This property tells it to look at our package-level annotations -->
<property name="annotatedPackages">
<list>
<value>com.example.model</value>
</list>
</property>
<property name="annotatedClasses">
<list>
<value>com.example.model.Entity1</value>
<value>com.example.model.Entity2</value>
...
</list>
</property>
</bean>

3. Where ever boolean or Boolean type properties are declared in the entity classes, tell hibernate to use our new custom type. This is done with the hibernate @Type annotation; just specify the name that we used to register our custom type above.

Entity1.java

package com.example.model;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

import org.hibernate.annotations.Type;

@Entity
public class Entity1
{
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;

private String name;

@Type(type="onezero-boolean")
private boolean active;

//Getters and Setters
...
}

That’s all there is to it. This same type can be applied to any and/or all boolean properties in our entities. Hibernate schema generation will generate TINY_INT columns for us and hibernate persistence will store boolean values as 1(true) and 0(false) in those columns.