Surprisingly
Hibernate 3 does not support UUID’s. I think the main reason for this is that UUID is not yet an ANSI database type. Each database manufacturer has implemented it in a different way, stored it as a slightly different type and has a driver (or set of drivers) that handle it differently!
For example,
MySQL has a
UUID() function that returns a 36 character string in the form aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee and expects you to store it in a VARCHAR(36) where as the .NET driver for
MySQL will spot that a database column is a BINARY(16) and automatically convert it for you.
There follows a post explaining how to plug a
java.util.UUID key into
Hibernate with a couple of the pitfalls that I fell into.
There are several supporting classes that you need to write, and a bit of configuration:
- Serialiser and de-serialiser for the java.util.UUID class
- java.util.UUID generator
- Hibernate type
The
UUIDUserType
class below was mostly lifted from the
Springframework forum site. Thanks bessette for the starting point. I have added the corrections he discovered.
This class takes care of converting a
java.util.UUID into something that the database column can coup with and converting the contents of a database column into a normal
java.util.UUID. More information can be found in the
JavaDoc for
UserType.
package uk.co.bigsoft.project.db.types ;
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 UUIDUserType implements UserType
{
private static final String CAST_EXCEPTION_TEXT = " cannot be cast to a java.util.UUID." ;
/*
* (non-Javadoc)
* @see org.hibernate.usertype.UserType#assemble(java.io.Serializable,
* java.lang.Object)
*/
public Object assemble (Serializable cached, Object owner) throws HibernateException
{
if (!String.class.isAssignableFrom (cached.getClass ()))
{
return null ;
}
return UUID.fromString ((String) cached) ;
}
/*
* (non-Javadoc)
* @see org.hibernate.usertype.UserType#deepCopy(java.lang.Object)
*/
public Object deepCopy (Object value) throws HibernateException
{
if (!UUID.class.isAssignableFrom (value.getClass ()))
{
throw new HibernateException (value.getClass ().toString () + CAST_EXCEPTION_TEXT) ;
}
UUID other = (UUID) value ;
return UUID.fromString (other.toString ()) ;
}
/*
* (non-Javadoc)
* @see org.hibernate.usertype.UserType#disassemble(java.lang.Object)
*/
public Serializable disassemble (Object value) throws HibernateException
{
return value.toString () ;
}
/*
* (non-Javadoc)
* @see org.hibernate.usertype.UserType#equals(java.lang.Object,
* java.lang.Object)
*/
public boolean equals (Object x, Object y) throws HibernateException
{
if (x == y)
return true ;
if (!UUID.class.isAssignableFrom (x.getClass ()))
{
throw new HibernateException (x.getClass ().toString () + CAST_EXCEPTION_TEXT) ;
}
else if (!UUID.class.isAssignableFrom (y.getClass ()))
{
throw new HibernateException (y.getClass ().toString () + CAST_EXCEPTION_TEXT) ;
}
UUID a = (UUID) x ;
UUID b = (UUID) y ;
return a.equals (b) ;
}
/*
* (non-Javadoc)
* @see org.hibernate.usertype.UserType#hashCode(java.lang.Object)
*/
public int hashCode (Object x) throws HibernateException
{
if (!UUID.class.isAssignableFrom (x.getClass ()))
{
throw new HibernateException (x.getClass ().toString () + CAST_EXCEPTION_TEXT) ;
}
return x.hashCode () ;
}
/*
* (non-Javadoc)
* @see org.hibernate.usertype.UserType#isMutable()
*/
public boolean isMutable ()
{
return false ;
}
/*
* (non-Javadoc)
* @see org.hibernate.usertype.UserType#nullSafeGet(java.sql.ResultSet,
* java.lang.String[], java.lang.Object)
*/
public Object nullSafeGet (ResultSet rs, String[] names, Object owner) throws HibernateException,
SQLException
{
String value = rs.getString (names[0]) ;
if (value == null)
{
return null ;
}
else
{
return UUID.fromString (value) ;
}
}
/*
* (non-Javadoc)
* @see
* org.hibernate.usertype.UserType#nullSafeSet(java.sql.PreparedStatement,
* java.lang.Object, int)
*/
public void nullSafeSet (PreparedStatement st, Object value, int index)
throws HibernateException, SQLException
{
if (value == null)
{
st.setNull (index, Types.VARCHAR) ;
return ;
}
if (!UUID.class.isAssignableFrom (value.getClass ()))
{
throw new HibernateException (value.getClass ().toString () + CAST_EXCEPTION_TEXT) ;
}
st.setString (index, value.toString ()) ;
}
/*
* (non-Javadoc)
* @see org.hibernate.usertype.UserType#replace(java.lang.Object,
* java.lang.Object, java.lang.Object)
*/
public Object replace (Object original, Object target, Object owner) throws HibernateException
{
if (!UUID.class.isAssignableFrom (original.getClass ()))
{
throw new HibernateException (original.getClass ().toString () + CAST_EXCEPTION_TEXT) ;
}
return UUID.fromString (original.toString ()) ;
}
/*
* (non-Javadoc)
* @see org.hibernate.usertype.UserType#returnedClass()
*/
@SuppressWarnings("unchecked")
public Class returnedClass ()
{
return UUID.class ;
}
/*
* (non-Javadoc)
* @see org.hibernate.usertype.UserType#sqlTypes()
*/
public int[] sqlTypes ()
{
return new int[] { Types.CHAR } ;
}
}
We are probably going to be using this UUID type for a lot of things, for example if it is a key field, there will be a corresponding column in another table.
I like one hibernate definition per file (which has so many advantages I could get another blog out of it!), so I need a new file to hold
UUIDUserType
’s indirection name so that we can reuse it in our other hibernate mapping files.
It would be sensible to call it
uuid or
guid but this will clash with existing (built-in) indirection names so we must pick another.
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<typedef name="bsuuid"
class="uk.co.bigsoft.project.types.UUIDUserType">
</typedef>
</hibernate-mapping>
The main reason for using UUID’s is that they are unique! So now we need a generator that generates them and returns them in the form we want. Hibernate has 2 uid generators; none of which fit the bill.
The first,
uuid, returns a uid in the form “aaaaaaaabbbbccccddddeeeeeeeeeeee” which is generated by hibernate and the second,
guid, returns it in the form “aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee” which is generated from a call to the database.
The problem with both of these is that they return the object as a
String, which means that your
POJO must have the id as a
String with the method signature of
setId(String id)
.
Hibernate infers that because the setter for id is a
String then id must be a
String. We want our id to be a UUID. We don’t want to duplicate getters and setters depending on what situation we are in.
The
UuidGenerator
below generates a
UUID which implements the
IdentifierGenerator hibernate interface and allows us to return a real
UUID which travels through hibernate as a
Serializable object, until it passes through
UUIDUserType
(above) and ends up in our POJO’s setter as a real
UUID.
package uk.co.bigsoft.project.db.types ;
import java.io.Serializable;
import java.util.UUID;
import org.hibernate.HibernateException;
import org.hibernate.engine.SessionImplementor;
import org.hibernate.id.IdentifierGenerator;
public class UuidGenerator implements IdentifierGenerator
{
@Override
public Serializable generate (SessionImplementor session, Object parent)
throws HibernateException
{
UUID u = UUID.randomUUID () ;
return u ;
}
}
Below is an example instance of a POJO class with a
UUID for an id:
package uk.co.bigsoft.project ;
import java.util.UUID;
public class MyClass
{
private UUID id ;
private String stuff ;
public void setId (UUID id)
{
this.id = id ;
}
public UUID getId ()
{
return id ;
}
public String getStuff ()
{
return stuff;
}
public void setStuff (String stuff)
{
this.stuff = stuff;
}
}
Finally we need to bring it all together,
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="uk.co.bigsoft.project.MyClass" table="my_class">
<id name="id" column="id" type="bsuuid" unsaved-value="null">
<generator class="uk.co.bigsoft.db.types.UuidGenerator" />
</id>
<property name="stuff" column="stuff" type="java.lang.String"/>
</class>
</hibernate-mapping>
This structure will work with any type of class not just
UUID, the only caveat is that is must implement the
Serializable interface.
Salut, La solution que tu donnes correspond parfaitement à mon problème. Je l’ai donc suivie scrupuleusement. Mais j’ai une erreur qui est générée suite à l’utilisation de l’UUID: [MrN: The solution you give fits to my problem. So I followed scrupulously. But I get an error that is generated as a result of using the UUID]
org.springframework.dao.InvalidDataAccessResourceUsageException: Could not execute JDBC batch update; nested exception is org.hibernate.exception.DataException: Could not execute JDBC batch update
Quelqu’un peut-il m’aider ? [MrN: Can anyone help me?] Merci d’avance