The one minute writer: hbm2java

hbm2java

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:

  • Writing everything by hand.
  • Putting xdoclet tags in your Java classes and generating the corresponding mapping file.
  • Generating a Hibernate mapping file and Java classes from the SQL schema.
  • Writing the Hibernate mapping files by hand, and generating Java classes and the SQL schema from the Hibernate mapping.
  • Writing the Hibernate mapping files by hand, based on a given SQL schema, and generating the Java classes using the hbm2java tool.

In this article, we will look at this last approach. Although such choices are often a matter of taste, this approach does have several advantages in many situations:

  • Hibernate mapping is centralized within the mapping files, rather than having the information dispersed among the Java source code, which can make maintenance easier. You also get a better control over the mapping, as in some cases, the XDoclet annotations do not support all of the functionality available in the Hibernate mapping schema.
  • The database schema can be maintained separately, rather than being generated from the classes or from the Hibernate mapping files. This allows the DBA, who may not be Java/Hibernate savvy, to have a better control over the nitty-gritty database details (indexes, tablespaces, table types, and so forth).

Generating Classes from the Mapping Files

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.)

Generating Java classes from the Hibernate mappings using hbm2java
Figure 1. Generating Java classes from the Hibernate mappings using
hbm2java

A Simple Class Generation Example

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.



"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">





A Book business object.
@author Duke









The unique ISBN code for this book.




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;
}
}

Generating the Classes for a Real-World Project

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.

Integrating into an Ant Build Process

Invoking hbm2java using Ant is fairly straightforward. First, you need to declare the hbm2java task so that Ant can
invoke it:



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:



description="Generate Java source code from the Hibernate mapping files">






Customizing the Build Process with Maven 1

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:

· The hbm2java.xml configuration file is expected to be in the src/hibernate directory.

· The Hibernate mapping files are expected to be in the src/hibernate directory.

· The Java classes will be generated in the src/generated/src/java directory.










targetfile="${maven.src.dir}/generated/hbm.jar">




value="${hibernateBuild.uptodate}"/>









classname="net.sf.hibernate.tool.hbm2java.Hbm2JavaTask"
classpathref="maven.dependency.classpath"/>


output="${maven.src.dir}/generated/src/java" >












Customizing the Build Process with Maven 2

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.


4.0.0
...

...


maven-antrun-plugin


generate-sources



classname="org.hibernate.tool.ant.HibernateToolTask"
classpathref="maven.dependency.classpath"/>









run







hbm2java in Hibernate 3

The 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:

· hibernate-tools.jar

· velocity-1.4.jar

· velocity-tools-generic-1.4.jar

· jtidy-r8-21122004.jar

· hibernate3.jar

· JDBC drivers

Then the task is declared as follows:







classname="org.hibernate.tool.ant.HibernateToolTask"
classpathref="maven.dependency.classpath"/>

Finally, you invoke the hbm2java task from within the hibernatetool task, as follows:



classname="org.hibernate.tool.ant.HibernateToolTask"
classpathref="maven.dependency.classpath"/>











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.

Customizing Generated Domain Classes

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.

The UML class diagram for the demo application

Figure 2. The UML class diagram for the demo application

The database schema used in 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.

For simple methods, you can use the class-code metaattribute to specify additional Java code from within the
Hibernatemapping file. For example, suppose we want a bidirectional

Placing Code in the Mapping File: The class-code Meta Attribute

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:



true


/**
* 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);
}

]]>

















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.

Using SQL Expressions

Sometimes the business logic (especially if it involves aggregations, sums, totals, and so on) may be more naturally

defined by an SQL expression:



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.

Using a Base Class

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:



true
CountryBase
protected














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);
}
}

Wrapper or Delegate Patterns

For more complex business logic, you may also want to use one of the following techniques:

  • You can define a "wrapper" or "delegate" class, which has the domain class as an attribute, and which provides additional business logic for a given domain object.
  • You may prefer a "service" or "facade" approach, where a "facade" object (such as a stateless session EJB) provides a set of related business services that manipulate domain objects.

Conclusion

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!

Resources

· Hibernate

· Hibernate Tools

· Ant

· Hibernate Ant tool

· Maven

0 comments :