Warning! Long story ahead… One of the great features of Hibernate is what it calls “…roundtrip engineering” where you can begin with one artifact (a POJO, a database table, a Hibernate mapping file) and produce all the others from it. You can start with hibernate mapping files (.hbm) that describe your objects and then generate basic POJO’s and your database schema (called DDM in Hibernate) using the Code Generation tools. You can also use Middlegen to generate your Hibernate Mapping Files directly from your database schema. Finally, you can produce mapping files from your compiled Java classes or from Java source with XDoclet markup (using HibernateDocletTask) which you could then use to produce DDM, which is what I spent time doing yesterday using Ant.
This is a new project, so I only had a couple Java classes that needed the Hibernate XDoclet tags added. I noticed yesterday that Cedric Beust had released a JavaDoc Tag Plug-in for Eclipse that ships with a definition file for Hibernate, which made it easy to get started with the Hibernate tags (download it, unzip to the Eclipse ‘plugins’ folder and restart Eclipse). A super simple example class looks like this:
package com.mycompany;
import java.io.Serializable;
/**
* @hibernate.class table="dog"
*/
public class Dog extends BaseBean implements Serializable {
private String name;
private String type;
public Dog() { }
/**
* @hibernate.property name="name" type="string"
* @hibernate.column name="name" length="255" not-null="true"
* @return
*/
public String getName() {
return name;
}
/**
* @hibernate.property name="type" type="string"
* @hibernate.column name="type" length="255" not-null="true"
* @return
*/
public String getType() {
return type;
}
public void setName(String name) {
name = name;
}
public void setType(String type) {
type = type;
}
}
After writing the class, the rest of your work is in Ant, which in turn uses XDoclet and the tools provided by Hibernate. You’ll need to download and extract XDoclet to your system; I added a property to my Ant build file that specifies the location of XDoclet:
<property name="xdoclet.home" value="C:\xdoclet-1.2.1"/>
I read other people who recommended adding XDoclet to the /lib/ directory of your Ant installation. After doing either of the above, you’ll need to define the HibernateDocletTask in Ant:
<taskdef name="hibernatedoclet"
classname="xdoclet.modules.hibernate.HibernateDocletTask">
<classpath refid="xdoclet.classpath"/>
</taskdef>
and then you can use it inside a <target>:
<hibernatedoclet
destdir="WEB-INF/classes"
excludedtags="@version,@author,@todo"
force="true"
mergedir="WEB-INF/classes"
verbose="false">
<fileset dir="src">
<include name="com/mycompany/*.java"/>
</fileset>
<hibernate version="2.0"/>
</hibernatedoclet>
In the above example I have the Hibernate mapping files being generated to /WEB-INF/classes from the Java source filesthat live in the /src/com/mycompany directory. However, this leaves you having to manually update the master Hibernate configuration file (hibernate.cfg.xml) with the references to the generated mapping files. The Hibernate site shows how you can generate the configuration file using BeanShell, but it’s easier than that. The latest version of XDoclet includes a task (HibernateCfgSubTask) that does it for you. Simple add this block of code:
<hibernatecfg
dataSource="java:comp/env/jdbc/pets"
showSql="false"
dialect="net.sf.hibernate.dialect.SQLServerDialect"
destDir="WEB-INF/classes" />
right after the <hibernate version="2.0" />
in the above example. In this example I’m using JNDI to look up a datasource called “pets”, I’m using the SQL Server Hibernate dialect, and I want the task to look in WEB-INF/classes for the Hibernate mapping files.
Wait! We’re not done yet! Because we now have Hibernate mapping files, we can generate a SQL script that will create the appropriate tables in the database. Again, the task needs to be defined:
<taskdef name="schemaexport" classname="net.sf.hibernate.tool.hbm2ddl.SchemaExportTask">
<classpath refid="compile.classpath"/>
</taskdef>
and then we can run the task:
<schemaexport
properties="src/hibernate.properties"
quiet="yes" text="true" drop="false" delimiter=";"
output="WEB-INF/classes/installation.sql">
<fileset dir="WEB-INF/classes" includes="**/*.hbm.xml"/>
</schemaexport>
The task requires that you specify a Hibernate dialect using a properties file (why not add dialect as an attribute like the Hibernatecfg task?) and then can either write the generated SQL to a file or can run the generated SQL against a database.
Anyway, all of this does lead up to a point. One of the puzzling things that I ran into was that I ended up specifying the type of the SQL column in my POJO using the @hibernate.column
tag like this:
@hibernate.column name="label" length="255" sql-type="nvarchar"
If you specify a sql-type then the length attribute is not passed through to the generated SQL, which leaves you with SQL that looks like this:
create table dog (
...
name VARCHAR,
type VARCHAR,
...
);
I’m not sure if this is by design or by accident, but Hibernate is open source, so it’s easy to find a work around. The offending code is in the net.sf.hibernate.mapping.Column class and the net.sf.hibernate.mapping.Table class. The Table class is responsible for looping over every column defined in the mapping file and then calls the getSqlType() method on the Column. The Column instance is then responsible for either a) guessing at the SQL type (if type is not defined) or b) returning the defined SQL type. In (a), Hibernate actually does return a length; it guesses that because the property is of type string, that the column should be of SQL type ‘varchar’ and that the length should be 255 characters. But in (b), instead of returning the type and then the length, it only returns type, which in SQL Server means that you’ll end up with all your varchar columns being 1 character in length. The workaround is to either go route (a) and let Hibernate guess what the column type and length should be or (b) you can specify length as part of the type. For instance:
@hibernate.column name="type" sql-type="nvarchar (50)"
which creates a nvarchar column of 50 characters. I know this syntax will work on MySQL as well as SQL Server, I’m not sure about the other database platforms that Hibernate supports. But the bigger question: bug or feature?