Monday, February 22, 2010

Grails-Hibernate Custom Data Type Mapping

Since the underlying ORM technology used by Grails is Hibernate, it is possible to manually specify how to persist datatypes by using Hibernate's UserType interface -- the class that establishes how to translates between a Java object and the db. This ability, in combination with custom property editors, provides a simple method of saving information from UI text input to domain objects to db and back.

For our example, we'll create a new datatype called SixDecimal, which stores a numerical value with 6 digit max precision. This class is essentially no different than BigDecimal, however we need to create a new datatype because UserTypes are associated with a specific class, and we want only to translate our new SixDecimal datatype using our UserType, not all BigDecimals in general.
class SixDecimal extends BigDecimal {
public SixDecimal (Double d) { super(d) }

public SixDecimal (java.math.BigDecimal bd) { super(bd.doubleValue()) }

Next we create the Hibernate UserType. The interface methods are described in the documentation. Of note is the SQL_TYPES definition. Originally, we intended to use Types.DECIMAL, as this translates into NUMBER(x,d) in Oracle, and would allow us to specify precision at the db level. However, this interferes with the Grails generation of the table for the domain class, so we resort to using Types.FLOAT, and handle the setting of precision in the custom property editor.
class SixDecimalUserType implements org.hibernate.usertype.UserType {
private static final SQL_TYPES = [Types.FLOAT] as int[]

public Object assemble(Serializable cached, Object owner) { cached }

public Object deepCopy(Object o) { o }

public Serializable disassemble(Object value) { value }

public boolean equals(Object x, Object y) { x.equals(y) }

public int hashCode(Object o) { o.hashCode() }

public boolean isMutable() { false }

public Object nullSafeGet(ResultSet rs, String[] names, Object owner) {
def result = rs.getBigDecimal(names[0])
if (result) { return new SixDecimal((java.math.BigDecimal)result) }
else {return null}

public void nullSafeSet(PreparedStatement st, Object value, int index) {
st.setBigDecimal(index, (java.math.BigDecimal)value)

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

public Class returnedClass() { SixDecimal }

public int[] sqlTypes() { SQL_TYPES }

Finally, we register our UserType with our new datatype. This can be accomplished system wide in Config.groovy
grails.gorm.default.mapping = {
'user-type'( type:SixDecimalUserType, class:SixDecimal )

or by field within a domain class
class MyDomain {
static mapping = {
interestRate type:SixDecimalUserType

SixDecimal interestRate

1 comment: