ONJava.com    
 Published on ONJava.com (http://www.onjava.com/)
 See this if you're having trouble printing code examples


Hibernate Class Generation Using hbm2java

by John Ferguson Smart
12/14/2005

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:

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:

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

Hibernate: A Developer's Notebook

Related Reading

Hibernate: A Developer's Notebook
By James Elliott

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.

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

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:

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

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:

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

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.

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

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.

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.

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

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

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:

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

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:

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

Wrapper or Delegate Patterns

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

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

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.