Development

Using Typed Id Values With Hibernate

Often you will want to make your ids strongly typed, so you are passing round a FooId, not a long.

You can do this in hibernate relatively easily. Once you’ve set up the base classes, adding new strongly typed ids is trivial.

Write the base class for your strongly typed class

There will be lots of different ones of these in your app probably.

package net.time4tea.types.id;
 
public class AbstractLongId<T extends AbstractLongId<T>> implements Comparable<T>, Serializable {
    protected final long value;
 
    public AbstractLongId(long value) {
        this.value = value;
    }
 
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
         
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
 
        AbstractLongId that = (AbstractLongId)o;
 
        return value == that.value;
    }
 
    // copied from java.lang.Long.hashCode()
    public int hashCode() {
        return (int)(value ^ (value >>> 32));
    }
 
    public long unwrapStaticTypeForLowLevelMapping() {
        return value;
    }
 
    public String toString() {
        return String.valueOf(value);
    }
 
    public int compareTo(T o) {
        return (value<o.value ? -1 : (value==o.value ? 0 : 1));
    }
}

Write your strongly typed class

package net.time4tea.types.id;
 
public class FooId extends AbstractLongId {
  public FooId(long id) {
   super(id);
  }
}

Set up your class

Set up your class with a strongly typed id, and set up the custom type mapping.

@Entity
public class Foo {
 
  [...]
  @Type(type = "net.time4tea.types.persist.id.PersistentFooId")
  private FooId fooId
 
}

A Base Persistent Object

This is quite handy in hibernate

package net.time4tea.types.persist;
 
public abstract class AbstractPersistentObject implements EnhancedUserType {
    public boolean equals(Object x, Object y) throws HibernateException {
        if (x == y) {
            return true;
        }
        if (x == null || y == null) {
            return false;
        }
 
        return x.equals(y);
    }
 
    public int hashCode(Object object) throws HibernateException {
        return object.hashCode();
    }
 
    public Object nullSafeGet(ResultSet resultSet, String[] strings, Object object) throws HibernateException, SQLException {
        return nullSafeGet(resultSet, strings[0]);
    }
 
    public abstract Object nullSafeGet(ResultSet resultSet, String string) throws SQLException;
 
    public boolean isMutable() {
        return false;
    }
 
    public Serializable disassemble(Object value) throws HibernateException {
        return (Serializable) value;
    }
 
    public Object assemble(Serializable cached, Object value) throws HibernateException {
        return cached;
    }
 
    public Object replace(Object original, Object target, Object owner) throws HibernateException {
        return original;
    }
 
    public String objectToSQLString(Object object) {
        throw new UnsupportedOperationException();
    }
 
    public String toXMLString(Object object) {
        return object.toString();
    }
}

Implement your base custom type mapper

package net.time4tea.types.persist.id;
 
public abstract class AbstractPersistentLongId
        extends AbstractPersistentObject
        implements SingleValueConverter
{
    private static final int[] SQL_TYPES = new int[]{Types.INTEGER};
 
    public int[] sqlTypes() {
        return SQL_TYPES;
    }
 
    public Object nullSafeGet(ResultSet resultSet, String string) throws SQLException {
        Long value = (Long) Hibernate.LONG.nullSafeGet(resultSet, string);
 
        return value == null ? null : newFromLong(value);
    }
 
    protected Object newFromLong(Long value) {
        try {
            return returnedClass().getConstructor(long.class).newInstance(value);
        } catch (RuntimeException e) {
            throw e;
        } catch (InvocationTargetException e) {
            throw new HibernateException("could not convert " + value + " to " + returnedClass(), e.getTargetException());
        } catch (Exception e) {
            throw new Defect(returnedClass() + " is not defined correctly for use as a Hibernated ID", e);
        }
    }
 
    public void nullSafeSet(PreparedStatement preparedStatement, Object value, int index) throws HibernateException, SQLException {
        if (value == null) {
            Hibernate.LONG.nullSafeSet(preparedStatement, null, index);
        } else {
            Hibernate.LONG.nullSafeSet(preparedStatement, ((AbstractLongId) value).unwrapStaticTypeForLowLevelMapping(), index);
        }
    }
 
    public Object fromXMLString(String xmlValue) {
        return newFromLong(Long.parseLong(xmlValue));
    }
 
    public Object deepCopy(Object value) throws HibernateException {
        return value;
    }
 
    public String toString(Object object) {
        return toXMLString(object);
    }
 
    public Object fromString(String xmlValue) {
        return fromXMLString(xmlValue);
    }
 
    public boolean canConvert(Class c) {
        return c == returnedClass();
    }
}

Implement your hibernate custom type mapper

Now you have all the right things in place to make your custom type mapper just work.

package net.time4tea.types.persist.id;
 
public class PersistentFooId extends AbstractPersistentLongId implements Serializable {
 public Class returnedClass() {
   return FooId.class;
 }
}

Conclusion.

Now each time you want to make a type safe id, you can just make a pair of typed objects… something that extends AbstractLongId, and something that extends AbstractPersistentLongId.

comments powered by Disqus