Yesterday, I started looking at implementing a State Pattern using Hibernate as the persistence mechanism. For the most part, I was following
Joe Kelley's excellent blog post on the subject here, in which he outlines two possible implementations using both an Integer, and the
org.apache.commons Enum type.
Both of these implementations are good solutions to the problem - they get the job done with minimal fuss.
However, I thought it might be useful to write about a third possible implementation, hinted at in Mr. Kelley's post - still based on his work, but tweaked to use a standard Java Enum as its main currency of change. It has the advantages of the described Enum implementation, but without bringing in extra libraries, or adding more complication than is strictly necessary.
To get this to work, realistically you only need to alter your implementation of the custom UserType. There are 4 things we need to change:
- Remove any static ints from your custom UserType. You won't need them.
- Alter the public int[] sqlTypes method to return a Types.VARCHAR.
- Alter the public Object nullSafeGet to convert the returned String value into your Java Enum value.
- Alter the public void nullSafeSet method to convert the concrete implementation of your State object to the String value required for persistence.
The first change is simple enough. We're moving responsibility for the actual values we're storing out of our UserType implementation, and into an Enum.
The second is to change the sqlTypes method. Here, we're merely telling Hibernate that what we expect our transactions with the data layer to be of type VARCHAR.
public int[] sqlTypes()
{
return new int[] {Types.VARCHAR};
}
The most interesting changes are the ones for nullSafeGet and nullSafeSet. We're using the GenericStateEnum to hold possible state values to be persisted. Using this method, you can manage explicitly what is being persisted, and enforce data-level constraints if you want.
nullSafeGet is fairly easy to understand - all we're doing here is taking our String value out of the ResultSet, attempting to convert it to an Enum value, and running a switch / case over it to determine which implementation of AbstractGenericState we should be returning to the calling class.
public Object nullSafeGet(ResultSet rs, String[] names, Object owner) throws HibernateException, SQLException
{
final String stateStr = rs.getString(names[0]);
if (rs.wasNull())
{
return null;
}
GenericState state = null;
GenericStateEnum stateValue = GenericStateEnum.valueOf(stateStr);
switch(stateValue)
{
case STATEONE:
state = AbstractGenericState.STATEONE;
break;
case STATETWO:
state = AbstractGenericState.STATETWO;
break;
default:
throw new RuntimeException("Could not find GenericState for value " + stateStr);
}
return state;
}
The method nullSafeSet is the interesting one:
public void nullSafeSet(PreparedStatement ps, Object value, int index) throws HibernateException, SQLException
{
if (value== null)
{
ps.setNull(index, Types.VARCHAR);
}
else
{
GenericState state = (GenericState) value;
GenereicStateEnum stateEnum = null;
if (state.isStateOne())
{
stateEnum = GenericStateEnum.STATEONE;
}
if (state.isStateTwo())
{
stateEnum = GenericStateEnum.STATETWO;
}
ps.setString(index, stateEnum.toString());
}
}
Now, I'm not particularly happy with this method at the moment in the way it's not particularly flexible, and I'm currently pondering ways to improve this. For example, what if we need to add another state? It's a relatively small change for sure, but is it one we can avoid?
Personally, I think the way forward with this is to use Reflection to work out the relationship between the implementation of GenericState used in the method, and the GenericStateEnum value. Certainly, it's not beyond the realms of possibility to solve. But as a starting point for using bog-standard Java Enums for State Pattern persistence using Hibernate, I've found it pretty effective.
Have a go, see what you think.