byte bohemian

20Mai/080

Hibernating JScience Measurements

Lately I discovered that nice little science API JScience, which is the reference implementation for the JSR-275. So I decided to use it on a little fun project I am currently working at. 30 minutes later I had stuffed the JAR into my local maven repository, added it to the POM file and integrated into my data model, when I realized that the values had to be stored into the database ...

Lately I discovered that nice little science API JScience, which is the reference implementation for the JSR-275. So I decided to use it on a little fun project I am currently working at. 30 minutes later I had stuffed the JAR into my local maven repository, added it to the POM file and integrated into my data model, when I realized that the values had to be stored into the database ...

I am using Hibernate at this toy project to persist the model into a MySQL database, so I had to find a clever way to store the JScience Measures with Hibernate.

I am using Hibernate quite a time at my job and we shared some good and some really bad moments together. So I already ran into similar problems i.e. if you want to store an address of a person you can use the embedding mechanism. But this time it was a kind of different. So I was taking a closer look to the Hibernate API and the Hibernate forum.

It took no lang time that the Interface UserType interface will provide me with the hook into Hibernate which I will need. I made the class a ParamterizedType too, because there where some stuff to configure into the type definition.

A JScience (JSR-275) measure consists of two entities, a quantity and a unit. JScience defines two kinds of measures a DecimalMeasure and a VectorMeasure. Mathematically the vector measure may be more interesting but I wanted to store weights and volume measures into my database so the decimal measure was enough.

So I stared my first attempt of my very own Hibernate type:

public class MeasureUserType implements UserType, ParameterizedType
{
	private Unit< ? extends Quantity> defaultUnit = null;

	public void setParameterValues(Properties parameters)	{
		this.defaultUnit = Unit.valueOf(
			parameters.getProperty("defaultUnit"));
	}
}

This is the framing of the type. The type definition and one property. The defaultUnit stores a default unit to create the measure and it offers a nice facility to store all values normalized into the database. You may set the value of the default unit to "g" (grams) and all of the values will be stored in grams. If you want to store the weight of containers into your database grams may be the wrong scale, so you can select "kg" kilograms oder "t" tons. Great isn't it! It has also a second even more important effect: You don't have to store the unit into your database. Imagine all the columns with almost the same values ("g", "kg", "mg", "t") in your database. This wouldn't be nice ...

Starting with the type you have to implement the sqlTypes Method which defines the SQL types Hibernate will use for SQL generation and persistence of values.

	public int[] sqlTypes()
	{
		return this.defaultUnit == null
			? new int[]{Types.NUMERIC, Types.VARCHAR}
			: new int[]{Types.NUMERIC};
	}

This implementation checks if a defaultUnit is set and returns a numeric type or a pair with a numeric and a varchar type. This leads to two different behaviors. If a default type only the numeric value of the metric measure will be stored. If no default value is set the value and the unit will be stored.

The next method will be the nullSafeSet method which inserts the value into a prepared statement Hibernate will use to insert/update the value(s).

public void nullSafeSet(PreparedStatement st, Object value, int index)
	throws HibernateException, SQLException
{
	if (value == null)
	{
		// if we have no value set the value of the measure to null
		st.setNull(index, sqlTypes()[0]);
		if (this.defaultUnit == null)
		{
			// if we have no default unit, set the unit to null too
			st.setNull(index + 1, sqlTypes()[1]);
		}
	}
	else
	{
		// otherwise insert the values
		if (this.defaultUnit == null)
		{
			// if we have no default unit, set the value of the measure
			Measure< ?, ?> measure = (Measure< ?, ?>) value;
			st.setObject(index, measure.getValue(),
			 	sqlTypes()[0]);
				// and the unit
				st.setObject(index + 1,
				measure.getUnit().toString(),
				sqlTypes()[1]);
		}
		else
		{
			// otherwise set only the value
			Measure< ?, ?> measure = (Measure< ?, ?>) value;
			st.setObject(index, measure.to((Unit)
				this.defaultUnit).getValue(), sqlTypes()[0]);
		}
	}
}

As you can see, it's quite simple :-) The only thing left to do is now to implement a method to fetch the values from the database. To do so Hibernate wants us to provide a nullSafeGet Method. So let's take a closer look:

public Object nullSafeGet(ResultSet resultSet,
	String[] names, Object owner) throws HibernateException,
	SQLException
{
	if (resultSet.wasNull())
	{
		// if the result set was null, the value is null
		return null;
	}

	Double value = resultSet.getDouble(names[0]);
	Unit unit = this.defaultUnit;
	if (value != null)
	{
		// if we have a value, check if a default unit exits
		if (this.defaultUnit == null)
		{
			// and set it if none exits
			unit = Unit.valueOf(resultSet.getString(names[1]));
		}

		// return the measure
		return Measure.valueOf(value, unit);
	}
	else
	{
		// the value is null, so return null
		return null;
	}
}

So now we can set and get these little javax.measure.Measure in the database, which works remarkable well. I put the complete source code on my website so you can download and take a closer look at the class if you like.

Creating a custom user type for hibernate is not so difficult as it looks at the first place and it's remarkable flexible too. Let's take a closer look how we can use it in an entity.

First we define a type definition with @TypeDef(s). The following type definitions define a type to store the mass and the energy.

@TypeDefs({
	@TypeDef(name = "mass", typeClass = MeasureUserType.class,
		parameters = {@Parameter(name = "defaultUnit", value = "g")}),
	@TypeDef(name = "energy", typeClass = MeasureUserType.class,
		parameters = {@Parameter(name = "defaultUnit", value = "kJ")})
})

Now we can use it in a class.

@Column(nullable = false)
@Type(type = "mass")
public Measure getBasicUnit()
{
	return this.basicUnit;
}

@Type(type = "energy")
public Measure getCalorificValue()
{
	return this.calorificValue;
}

As you can see the first attribute stores a basic unit for a mass and the second one the calorific unit as an energy measure.

There is nothing more to say I guess. Or let's say a little different I have nothing more to say at this moment. I hope you liked this little article, I enjoyed sharing my thoughts with you.

veröffentlicht unter: Hibernate, Java, Technology Kommentar schreiben
Kommentare (0) Trackbacks (0)

Zu diesem Artikel wurden noch keine Kommentare geschrieben.


Kommentar schreiben


Noch keine Trackbacks.

Search engine optimization by SEO Design Solutions