Thursday 21 February 2008

UUID and Hibernate - update

I have finally worked out the annotations for using a custom UUID generator as per the previous post. The Hibernate documentation is not that clear on what is required, though it is in there. To quote:

"@org.hibernate.annotations.GenericGenerator allows you to define an Hibernate specific id generator. ...strategy is the short name of an Hibernate3 generator strategy or the fully qualified class name of an IdentifierGenerator implementation. You can add some parameters through the parameters attribute."

Now that may be clearer to you, than it was to me. And other than using some examples of their own custom generators as supplied by Hibernate that is it. And all the examples on the net lazily just use the same as what is in the Hibernate documentation.

Unfortunately, the Hibernate documented uuid example:
@Id @GeneratedValue(generator="system-uuid")
@GenericGenerator(name="system-uuid", strategy = "uuid")
public String getId() {
generates a 32 character unformatted hex string. You can also use the parameter attribute as suggested by the documentation to format it with a hyphen "-" separator, but as per my previous post, this returns an incorrectly formatted UUID that postgresql does not accept, which is the whole reason for this post in providing a solution to that shortcoming.

NB. this solution is dependent on the org.hibernate.annotations.GenericGenerator annotation and the org.hibernate.annotations.Type annotation, so it is not a pure EJB3 solution. The org.hibernate.validator.NotNull annotation is not really necessary in this case, especially as it duplicates the nullable = false attribute of the @column annotation.

The my_custom_uuid attribute value for the generator and the name attributes, can be anything that is meaningful to you, as long as they match in both the @GeneratedValue and the @GenericGenerator annotations.

An annotation example for the custom UUID generator is as follows:

@Id
@GeneratedValue(generator="
my_custom_uuid")
@GenericGenerator(name="
my_custom_uuid", strategy = "fully.qualified.path.CustomUUIDGenerator")
@Column(name = "id", unique = true, nullable = false, length = 36, columnDefinition="uuid")
@Type(type="fully.qualified.path.CustomUUIDType")
@NotNull
public String getId() {
return this.id;
}

public void setId(String id) {
this.id= id;
}

I probably should also mention here, as I overlooked doing so in the previous post, that there are differences in how to construct a Hibernate SessionFactory between supporting *.hbm.xml files and annotations. Ostensibly they can be mixed in the same project, but your mileage will probably vary in doing so. Personally, I have found some issues in attempting to have both methods coexist.

Creating a *.hbm.xml SessionFactory can be done as per the documented examples:
sessionFactory = new Configuration().configure().buildSessionFactory();
Additionally, within the *.hbm.xml file, the Entity classes are resource attributes of the mapping element, similar to the following:

<hibernate-configuration>
<session-factory name="PostgreSQLFactory">
<property name="hibernate.dialect">org.hibernate.dialect.PostgreSQLDialect</property>
...
<mapping resource="relative/path/PlainEntity1.hbm.xml"/>
<mapping resource="relative/path/PlainEntity2.hbm.xml"/>
</session-factory>
</hibernate-configuration>

However, the annotated SessionFactory has some gotchas that again, are not really covered that well in the Hibernate documentation.

The Hibernate example has the following:
sessionFactory = new AnnotationConfiguration().buildSessionFactory();
But this is incorrect, and will not work. In my experience, you actually need to do something similar to the following:
sessionFactory = new AnnotationConfiguration().configure().buildSessionFactory();
Additionally, unless the session factory has information about the target database, such as the dialect and connection details in the hibernate.cfg.xml file, it will fail at runtime. Significantly, the examples in the Hibernate documentation do not include these values. So if you are following the Hibernate documentation, make sure that the configure() call can get these details from somewhere, even if they are missing from the examples. Another thing to be mindful of, is that the mapping attributes for *.hbm.xml files use a resource attribute, while for annotations, it uses class attributes.

<hibernate-configuration>
<session-factory name="PostgreSQLFactory">
<property name="hibernate.dialect">org.hibernate.dialect.PostgreSQLDialect</property>
...
<mapping class="relative.path.AnnotatedEntity1"/>
<mapping class="relative.path.AnnotatedEntity2"/>
</session-factory>
</hibernate-configuration>

Because I have a number of different hibernate.cfg.xml files in my tests, I take an approach similar to the following:

File configFile = new File("relative_path/hibernate.cfg.xml");

AnnotationConfiguration config = new AnnotationConfiguration();
config.configure(configFile);
sessionFactory = config
.addPackage("fully.qualified.package")
.addAnnotatedClass(AnnotatedEntity1.class)
.addAnnotatedClass(AnnotatedEntity2.class)
.addAnnotatedResource(relative/path/
PlainEntity1.hbm.xml)
.addAnnotatedResource(relative/path/
PlainEntity2.hbm.xml)
.buildSessionFactory();

I have provided this example only so that you can see that there are a number of construction alternatives available that you can play with, to meet a variety of needs. Also be mindful that the same entity cannot have an entry in a hibernate.cfg.xml file as well as registered programmatically as per the above example.

Now I need to decide if I should bother spending any time on creating a Hibernate reverse engineering template to auto-create this correctly from a postgresql db. Hmmm, maybe not.

No comments: