Hopefully you've solved this by now, but here is the hack I have come up with for posterity.
I created a user type which is basically a copy of org.hibernate.type.PostgresUUIDType and added this to the UUID columns:
Code:
package net.zorq.censeo.domain;
import org.hibernate.type.AbstractSingleColumnStandardBasicType;
import org.hibernate.type.descriptor.ValueBinder;
import org.hibernate.type.descriptor.ValueExtractor;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.java.JavaTypeDescriptor;
import org.hibernate.type.descriptor.java.UUIDTypeDescriptor;
import org.hibernate.type.descriptor.sql.BasicBinder;
import org.hibernate.type.descriptor.sql.BasicExtractor;
import org.hibernate.type.descriptor.sql.SqlTypeDescriptor;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.UUID;
/**
* @author David Beaumont
* @see org.hibernate.type.PostgresUUIDType
*/
public class UUIDType extends AbstractSingleColumnStandardBasicType<UUID> {
public static final UUIDType INSTANCE = new UUIDType();
public UUIDType() {
super( UUIDSqlTypeDescriptor.INSTANCE, UUIDTypeDescriptor.INSTANCE );
}
@Override
public String getName() {
return "custom-uuid";
}
public static class UUIDSqlTypeDescriptor implements SqlTypeDescriptor {
public static final UUIDSqlTypeDescriptor INSTANCE = new UUIDSqlTypeDescriptor();
@Override
public int getSqlType() {
return Types.OTHER;
}
@Override
public boolean canBeRemapped() {
return true;
}
@Override
public <X> ValueBinder<X> getBinder(final JavaTypeDescriptor<X> javaTypeDescriptor) {
return new BasicBinder<X>( javaTypeDescriptor, this ) {
@Override
protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException {
/**
* This check ensures the normal path is taken for UUIDs on
* other databases. This is the single place where this
* class differs from that bundled in Hibernate at
* org.hibernate.type.PostgresUUIDType
*/
if(st.getConnection().getMetaData().getDriverName().equals("org.postgresql.Driver")) {
st.setObject( index, javaTypeDescriptor.unwrap( value, UUID.class, options ), Types.OTHER );
} else {
st.setObject( index, javaTypeDescriptor.unwrap( value, UUID.class, options ));
}
}
};
}
@Override
public <X> ValueExtractor<X> getExtractor(final JavaTypeDescriptor<X> javaTypeDescriptor) {
return new BasicExtractor<X>( javaTypeDescriptor, this ) {
@Override
protected X doExtract(ResultSet rs, String name, WrapperOptions options) throws SQLException {
return javaTypeDescriptor.wrap( rs.getObject( name ), options );
}
};
}
}
}
The only difference is that before PreparedStatement.setObject is called, a check is done to see which database driver is in use. In my test cases, I am using H2 rather than postgresql. Some more work is needed, as with the user type, H2 doesn't know how to create the columns for JDBC type Types.OTHER. To circumvent this, I created a new dialect:
Code:
package net.zorq.censeo.util;
import org.hibernate.HibernateException;
import java.sql.Types;
/**
* Needed to keep UUIDs in H2 working after specifying a user type to make
* UUIDs use Postgres' native type in production.
* This needs doing because in Hibernate 4 you can no longer add type overrides
* per dialect. See: https://forum.hibernate.org/viewtopic.php?p=2451813
* @see net.zorq.censeo.domain.UUIDType
* @author David Beaumont
*/
public class H2Dialect extends org.hibernate.dialect.H2Dialect {
public String getTypeName(int code, long length, int precision, int scale) throws HibernateException {
if(code == Types.OTHER) {
return "binary(16)";
} else {
return super.getTypeName(code, length, precision, scale);
}
}
}
Then in my test classes, I make sure that new dialect is the one used. This all seems very fragile (only works for two specified databases) so it would if any Hibernate devs are listening, what's the proper way of achieving this?
David