struts2spring

March 26, 2009

hbm2java

Filed under: Hibernate, Technology — struts2spring @ 7:51 AM
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 has 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.

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

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

  • 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

About these ads

Leave a Comment »

No comments yet.

RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

The Silver is the New Black Theme. Blog at WordPress.com.

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: