Hibernate is a popular
open source library for handling object/relational persistence and
queries. In Hibernate, mapping between database tables and POJO
("plain old Java objects") classes is configured in a set of XML
mapping files. hbm2java is a code generator that
converts the mapping files into POJOs. It is part of the Hibernate Tools subproject
and can be downloaded in the separate
Hibernate Extensions package.
Several strategies exist for managing Hibernate mapping files, such as:
xdoclet
tags in your Java classes and generating the corresponding mapping
file.hbm2java tool.In this article, we will look at this last approach. Although such choices are often a matter of taste, this approach does has several advantages in many situations:
In this approach, the Hibernate mapping files are king. All
Hibernate mapping information is centralized in these files,
meaning no annotations are used in the source code. All persistent
classes are generated using the hbm2java tool. The
classes cannot be modified afterwards.
This process is illustrated in Figure 1. First, you take the set
of Hibernate mapping files. You may also need a hbm2java
configuration file, generally called hbm2java.xml. Using
these two entries, the hbm2java tool generates one or
more Java classes for each Hibernate mapping file. The hbm2java
configuration file can be useful for fine-tuning the
class-generation process. (In Hibernate 3, this file is no longer
used.)
Figure 1. Generating Java classes from the Hibernate mappings
using hbm2java
|
Related Reading Hibernate: A Developer's Notebook |
|
Let's start with a very simple example. Suppose we want to map a
simple table called BOOK, as described here:
Column | Type | Modifiers
------------+-----------------------+-----------
BOOK_ID | character(32) | not null
BOOK_TITLE | character varying(80) | not null
BOOK_ISBN | character varying(20) | not null
To generate this class, we could use the following Hibernate mapping file. Note how meta-attributes can be used to add comments or fine-tune class generation.
<?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="Book" table="BOOK">
<meta attribute="class-description">
A Book business object.
@author Duke
</meta>
<id name="id" type="string" unsaved-value="null" >
<column name="BOOK_ID" sql-type="char(32)" not-null="true"/>
<generator class="uuid.hex"/>
</id>
<property column="BOOK_NAME" name="name"/>
<property column="BOOK_ISBN" name="isbn">
<meta attribute="field-description"/>
The unique ISBN code for this book.
</meta>
</property>
</class>
</hibernate-mapping>
Using this mapping file, the hbm2java will generate
a class that looks something like this:
/**
* A Book business object.
* @author Duke
*/
public class Book {
private String id;
private String name;
private String isbn;
public Book() {
}
public String getId() {
return id;
}
private void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
/**
* The unique ISBN code for this book.
*/
public String getIsbn() {
return isbn;
}
public void setIsbn(String isbn) {
this.isbn = isbn;
}
}
|
Actually, hbm2java is designed to convert
one hibernate mapping file into a corresponding set of
Java classes. If you want to use this approach for a real
application, it would be obviously more convenient to generate the
classes for all of the Hibernate mapping files in one fell
swoop. The best way to do this is integrate the class-generation
task into your automated build process.
Invoking hbm2java using Ant is fairly straightforward. First,
you need to declare the hbm2java task so that Ant can
invoke it:
<taskdef name="hbm2java"
classname="net.sf.hibernate.tool.hbm2java.Hbm2JavaTask"
classpathref="project.class.path"/>
Next, you use this task; for example, by writing a target to
generate source code for all of the *.hbm.xml files in the
source directory. Suppose ${src.hibernate} represents the
directory containing the Hibernate mapping files, and
${src.generated} the directory where you want the source
code to go. The Ant task could look something like this:
<target name="codegen"
description="Generate Java source code
from the Hibernate mapping files">
<hbm2java output="${source.generated}">
<fileset dir="${src.hibernate}">
<include name="**/*.hbm.xml"/>
</fileset>
</hbm2java>
</target>
|
To integrate this into a Maven (1.0) build process, you need to
modify your maven.xml file. The Maven code is presented
here. The script basically checks whether the Hibernate mapping
files have been changed since the last time the classes were
generated (using the uptodate tag), and, if not,
invokes the Ant hbm2java task described earlier. In
this case, the following assumptions are made:
<goal name="generate-hibernate-classes">
<ant:echo message="Hibernate class generation"/>
<fileset dir="${basedir}/src" id="fileset.hbm.xml">
<include name="**/*.hbm.xml"/>
</fileset>
<uptodate property="hibernateBuild.uptodate"
targetfile="${maven.src.dir}/generated/hbm.jar">
<srcfiles refid="fileset.hbm.xml"/>
</uptodate>
<j:set var="buildHibernateFiles"
value="${hibernateBuild.uptodate}"/>
<j:choose>
<j:when test="${buildHibernateFiles != null}">
<ant:echo message="Hibernate classes up to date"/>
</j:when>
<j:otherwise>
<ant:echo message="Generating Hibernate classes to src/java"/>
<ant:taskdef name="hbm2java"
classname="net.sf.hibernate.tool.hbm2java.Hbm2JavaTask"
classpathref="maven.dependency.classpath"/>
<ant:hbm2java config="${maven.src.dir}/conf/hbm2java.xml"
output="${maven.src.dir}/generated/src/java" >
<ant:fileset dir="${maven.src.dir}/hibernate">
<ant:include name="**/*.hbm.xml"/>
</ant:fileset>
</ant:hbm2java>
<ant:jar jarfile="${maven.src.dir}/generated/hbm.jar">
<fileset refid="fileset.hbm.xml"/>
</ant:jar>
</j:otherwise>
</j:choose>
</goal>
If you happen to be using Maven 2, things are a little simpler.
Instead of using pre and post goals in the maven.xml file,
you add a maven-antrun-plugin plugin to the
pom.xml file. Within this plugin, in the
tasks section, you can directly invoke the Ant tasks,
described above.
<project...>
<modelVersion>4.0.0</modelVersion>
...
<build>
...
<plugins>
<plugin>
<artifactId>maven-antrun-plugin</artifactId>
<executions>
<execution>
<phase>generate-sources</phase>
<configuration>
<tasks>
<taskdef name="hibernatetool"
classname="org.hibernate.tool.ant.HibernateToolTask"
classpathref="maven.dependency.classpath"/>
<hbm2java output="src/generated">
<fileset dir="src/hibernate">
<include name="**/*.hbm.xml"/>
</fileset>
</hbm2java>
</tasks>
</configuration>
<goals>
<goal>run</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
|
hbm2java in Hibernate 3The hbm2java tool has undergone a major overhaul in
Hibernate 3. In the new version of the Hibernate Tools (still in
alpha at the time of writing), the hbm2java task has been
integrated, among other similar tasks, into the
hibernatetool task. The Ant task will need to find the
following .jar files on the class path:
Then the task is declared as follows:
<taskdef name="hibernatetool"
classname="org.hibernate.tool.ant.HibernateToolTask"
classpathref="maven.dependency.classpath"/>
Finally, you invoke the hbm2java task from within
the hibernatetool task, as follows:
<taskdef name="hibernatetool"
classname="org.hibernate.tool.ant.HibernateToolTask"
classpathref="maven.dependency.classpath"/>
<hibernatetool destdir="src/main/generated/src/java">
<configuration configurationfile="src/main/hibernate/hibernate.cfg.xml">
<fileset dir="src/main/hibernate">
<include name="**/*.hbm.xml"/>
</fileset>
</configuration>
<hbm2java />
</hibernatetool>
Note that the Hibernate 3 version of the tool, while very
promising, is still in an alpha version at the time of writing, so
it should be used with caution.
Now you know how to generate Java source code from the Hibernate mappings. What next?
To discuss some finer details, we will use a simple class model,
illustrated in Figures 2 and 3. The class model represents an
Employees database. Each employee is assigned to a country, and
speaks one or more languages. Each country also has a set of
international airports.

Figure 2. The UML class diagram for the demo application

Figure 3. The database schema used in the demo
application
Sometimes you may want to add domain logic into your domain classes. Indeed, for many people, the main disadvantage of generating the Java classes is that the domain classes become relatively passive; it is not easy to add business logic methods into the generated domain classes, which arguably makes them somewhat less "object-oriented." There is no one-size-fits-all solution to this problem, but a few possible approaches are described here.
|
class-code Meta
AttributeFor simple methods, you can use the class-code meta
attribute to specify additional Java code from within the Hibernate
mapping file. For example, suppose we want a bidirectional
relation between a country and its airports. Whenever we add an
airport to a Country object, we want to be sure to set the
reciprocal country attribute in the Airport object. We can
do this as follows:
<class name="Country" table="COUNTRY" dynamic-update="true">
<meta attribute="implement-equals">true</meta>
<meta attribute="class-code">
<![CDATA[
/**
* Add an airport to this country
*/
public void addAirport(Airport airport) {
airport.setCountry(this);
if (airports == null) {
airports = new java.util.HashSet();
}
airports.add(airport);
}
]]>
</meta>
<id name="id" type="long" unsaved-value="null" >
<column name="cn_id" not-null="true"/>
<generator class="increment"/>
</id>
<property column="cn_code" name="code" type="string"/>
<property column="cn_name" name="name" type="string"/>
<set name="airports" >
<key column="cn_id"/>
<one-to-many class="Airport"/>
</set>
</class>
This approach is not particularly satisfying, except for very small
methods: writing Java code in XML files tends to be error-prone
and difficult to maintain.
Sometimes the business logic (especially if it involves aggregations, sums, totals, and so on) may be more naturally defined by an SQL expression:
<property name="orderTotal" type="java.lang.Double"
formula="(select sum(item.amount)
from item
where item.order_id = order_id)" />
This is an elegant solution in many cases. However, you should be aware that this query will be executed every time the object is loaded from the database, so it may penalize performance.
You can also use the generated-class meta attribute to
define a base class, which will be generated by
hbm2java, leaving you free to place your business
logic in a subclass of this generated class. For example, using
this technique for the Country class could be done as
follows:
<class name="Country" table="COUNTRY" dynamic-update="true">
<meta attribute="implement-equals">true</meta>
<meta attribute="generated-class">CountryBase</meta>
<meta attribute="scope-field">protected</meta>
<id name="id" type="long" unsaved-value="null" >
<column name="cn_id" not-null="true"/>
<generator class="increment"/>
</id>
<property column="cn_code" name="code" type="string"/>
<property column="cn_name" name="name" type="string"/>
<set name="airports" >
<key column="cn_id"/>
<one-to-many class="Airport"/>
</set>
</class>
hbm2java will generate the CountryBase
class, containing all the attributes, getters, setters, etc.
described by the mapping file. Then you are free to place your
business logic in the derived class called Country,
which will be used and instantiated by Hibernate; for example:
public class Country extends CountryBase {
/**
* Add an airport to this country
*/
public void addAirport(Airport airport) {
airport.setCountry(this);
if (getAirports() == null) {
setAirports(new java.util.HashSet());
}
getAirports().add(airport);
}
}
For more complex business logic, you may also want to use one of the following techniques:
This article describes one approach we used to manage Hibernate mappings, which worked well in our particular circumstances. There are, of course, many others. Maybe this article will provide some ideas for your projects, but whatever you do, use whatever suits your project best!
John Ferguson Smart is a freelance consultant specializing in Enterprise Java, Web Development, and Open Source technologies, currently based in Wellington, New Zealand.
Return to ONJava.com.
Copyright © 2009 O'Reilly Media, Inc.