1

I want to implement the following workflow: I design tables in postgresql 9, I use Hibernate Tools from Eclipse Indigo to generate POJOs for these tables, and I want Hibernate to use annotations. Using Eclipse Indigo, latest Postgresql JDBC driver, Java 1.6, and Hibernate tools from Eclipse marketplace, this does not work.

Reverse engineering wizard sees UUID fields of the table as OTHER, and it generates fields with type Serializable, instead of UUID. Inserting POJOS complain about an attempt to insert bytea data to UUID fields. Simply put: how do I reverse engineer POJOS using hibernate tools so that UUID is handled automatically?

here is the generated field:

private Serializable instanceId;

mahonya
  • 9,247
  • 7
  • 39
  • 68

1 Answers1

2

Custom UserType

You need to use a custom usertype that tells Hibernate how to serialize and deserialize the UUID columns to java objects. Here's mine (using Hibernate 3.6.8.Final):

package your.package.usertype;

import java.io.Serializable;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.UUID;

import org.hibernate.HibernateException;
import org.hibernate.usertype.UserType;

public class UUIDType implements UserType {
    private final int[] sqlTypesSupported = new int[] { Types.NUMERIC };
    private final String CAST_EXCEPTION_TEXT = " cannot be cast to a java.util.UUID"; 

    public int[] sqlTypes() {
        return sqlTypesSupported;
    }

    @SuppressWarnings("rawtypes")
    public Class returnedClass() {
        return UUID.class;
    }

    public boolean equals(Object x, Object y) throws HibernateException {
        if (x == null) {
            return y == null;
        } else {
            return x.equals(y);
        }
    }

    public int hashCode(Object x) throws HibernateException {
        return x == null ? null : x.hashCode();
    }    

    public Object nullSafeGet(ResultSet rs, String[] names, Object owner) throws HibernateException, SQLException {
        assert(names.length == 1);
        Object value = rs.getObject(names[0]);
        if (value == null) {
            return null;
        }        

        UUID uuid = UUID.fromString( rs.getString( names[0] ) );
        return rs.wasNull() ? null : uuid;
    }

    public void nullSafeSet(PreparedStatement st, Object value, int index) throws HibernateException, SQLException {
        if (value == null) {
            st.setNull(index, Types.NULL);
            return;
        } 

        if (!UUID.class.isAssignableFrom(value.getClass())) {
            throw new HibernateException(value.getClass().toString() + CAST_EXCEPTION_TEXT);
        }        

        UUID uuid = (UUID) value;
        st.setObject(index, uuid, Types.OTHER);
    }

    public Object deepCopy(Object value) throws HibernateException {
        if (value == null)
            return null;
        UUID uuid = (UUID) value;
        return new UUID( uuid.getMostSignificantBits(), uuid.getLeastSignificantBits() );
    }

    public boolean isMutable() {
        return false;
    }

    public Serializable disassemble(Object value) throws HibernateException {
        return (Serializable) value;
    }

    public Object assemble(Serializable cached, Object owner) throws HibernateException {
        return cached;
    }

    public Object replace(Object original, Object target, Object owner) throws HibernateException {
        return original;
    }
}

Hibernate mappings

Your Hibernate mappings need to specify the classname of your user type for the uuid properties:

<property name="uuid" type="your.package.usertype.UUIDType">
    <column name="uuid" not-null="true" />
</property>

Reverse engineering

Since you're using code generation, you need to tell the reverse engineering process to map these columns to your custom UUIDType usertype. You should be able to do this in the hibernate.reveng.xml file by mapping all OTHER types to UUIDType (untested):

<type-mapping>
    <sql-type jdbc-type="OTHER" hibernate-type="your.package.usertype.UUIDType" />
</type-mapping>

or by specifying specific table/columns:

<table name="your_table">
    <column name="uuid" type="your.package.usertype.UUIDType" />
</table>

or, instead of using hibernate.reveng.xml you can have finer control over the reverse engineering process by creating your own ReverseEngineeringStrategy class:

package your.package.reveng;

import org.hibernate.cfg.reveng.DelegatingReverseEngineeringStrategy;
import org.hibernate.cfg.reveng.ReverseEngineeringStrategy;
import org.hibernate.cfg.reveng.TableIdentifier;

public class CustomReverseEngineeringStrategy extends DelegatingReverseEngineeringStrategy {
    public CustomReverseEngineeringStrategy( ReverseEngineeringStrategy delegate ){
        super(delegate);
    }

    public String columnToHibernateTypeName( TableIdentifier table, String columnName, int sqlType, int length, int precision, int scale, boolean nullable, boolean generatedIdentifier ){
        if( table.getName().equals("your_table") && columnName.equals("uuid") ){
            return "your.package.usertype.UUIDType";
        } else {
            return super.columnToHibernateTypeName( table, columnName, sqlType, length, precision, scale, nullable, generatedIdentifier );
        }
    }
}

Edit: it looks like org.hibernate.type.PostgresUUIDType exists since 3.6.0.Beta1. You may be able to just use this type, but I think that you'll still have to specify it in your mappings and direct reverse engineering to use it for your desired columns.

Shane
  • 4,179
  • 1
  • 28
  • 26