UserType for a byte[] identifier property

Almost any Java type may be used as a generated Hibernate identifier. In this example we show how to use a byte array.

Java arrays don't override equals() (which is required for Hibernate identifiers) so we must wrap the byte array in a class.

public final class Bytes implements Serializable {
   private final byte[] _bytes;
   public Bytes(byte[] bytes) {
      _bytes=bytes;
   }
   public boolean equals(Object other) {
      if (other==null || !(other instanceof Bytes) ) return false;
      return Arrays.equals( ( (Bytes) other )._bytes, _bytes );
   }
   public byte[] getBytes() {
      byte[] clone = new byte[_bytes.length];
      System.arraycopy(_bytes, 0, clone, 0, _bytes.length);
      return clone;
   }
   public String toString() {
      //well worth implementing.....
   }
}

 

We require a custom type for Bytes. The implementation above does not expose the underlying byte array to clients, so its safe to define this as an immutable type.

public class BytesType implements UserType {
    
    private static final int[] SQL_TYPES = { Types.VARBINARY };
    
    public int[] sqlTypes() {
        return SQL_TYPES;
    }
    public boolean isMutable() {
        return false;
    }
    public Class returnedClass() {
        return Bytes.class;
    }
    public boolean equals(Object x, Object y) {
        return (x==y) || ( x!=null && y!=null && x.equals(y) );
    }
    public Object deepCopy(Object value) {
        return value;
    }
    public Object nullSafeGet(ResultSet rs, String[] names, Object owner) 
        throws HibernateException, SQLException {
        byte[] bytes = rs.getBytes(names[0]);
        if ( rs.wasNull() ) return null;
        return new Bytes(bytes);
    }
    public void nullSafeSet(PreparedStatement st, Object value, int index) 
        throws HibernateException, SQLException {
        if (value==null) {
           st.setNull(index, Types.VARBINARY);
        }
        else {
           st.setBytes( index, ( (Bytes) value ).getBytes() );
        }
    }

}

 

Now we need to define an IdentifierGenerator. We will reuse one of Hibernate's built-in UUID algorithms.

public class BytesGenerator implements IdentifierGenerator {

    private IdentifierGenerator uuid = new UUIDStringGenerator();

    public Serializable generate(SessionImplementor session, Object object)
        throws HibernateException, SQLException {
        
        return new Bytes( ( (String) uuid.generate(session, object) ).getBytes() );
    }

}

 

Now an identifier property could be defined and mapped as follows:

private Bytes _id;
public Bytes getId() { return _id; }
private void setId(Bytes id) { _id=id; }
<id name="id" type="BytesType" unsaved-value="null">
   <generator class="BytesGenerator"/>
</id>