Persistence with XDoclet and Hibernate

Posted Tue, 07 Mar 2006 20:17:00 GMT

Java 5 annotations are all very nice, but most of us, especially in larger companies, are still stuck with Java 1.4 and even 1.3.

In the meantime we can get some of that goodness and develop our persistence layer quickly and almost transparently by using XDoclet and Hibernate. This will not deal with the transactional and session management issues solved by EJB3, but it will give you a very fast and highly maintainable approach for generating your persistence layer.

This is achieved by using XDoclet and Hibernate’s hbm2ddl utility to eliminate the need for maintaining XML mapping files and keeping your development database up to date.

You still need to know Hibernate to use it. There are some quite intricate areas there such as session management, transaction management, object identity, lazy loading, etc, which are not considered here. Here’s a good book - Hibernate In Action by Gavin King and Christian Bauer.

The whole example is available for download as a zipped Eclipse workspace from here. Extract this in an existing workspace and map the eclipse variables.

What we’ll do here

  • Download XDoclet and Hibernate.
  • Write our model classes.
  • Annotate them using XDoclet tags
  • Write the ant script to build the Hibernate mappings and the database and DDL
  • Write tests

Download the required dependencies

The required software is:

XDoclet2 Plugins 1.0.3 - download here - http://sourceforge.net/project/showfiles.php?group_id=88133&package_id=92059&release_id=378159.

Hibernate 3.1 - download here - http://sourceforge.net/project/showfiles.php?group_id=40712&package_id=127784&release_id=388971.

Hibernate Tool - find it from the Hibernate homepage. I dislike the packaging a bit - it seems to violate the RRE principle - the eclipse plugin and the ant tasks are packaged together.

The model classes

I’ve got a pretty simple model here with four objects - Event, Participant, User and Bet. An event has many participants each given odds for that particular event. A User is an independent object. A Bet has a user, an event and a participant. This makes the model quite intricate despite its simplicity, but again, I am not demonstrating Hibernate features here, just trying to show that even the more complex mappings can be achieved through XDoclet annotations. For example, the Map in the Event, which is keyed on Participant and contains the odds as values - this is a pretty complex ORM concept, but achieved here quite simply.

Graphical representation of the model

The XDoclet annotations

Simple property mapping

… is simple - like this:


    /**
     * @hibernate.property
     */
    private String name;

But XDoclet can generate pretty much everything that Hibernate can conceive. Like this pretty complex Map:


    /** 
     * The odds (Float), mapped on participants (Participant)
     *
     * @hibernate.map
     *   cascade="save-update"
     * @hibernate.map-key-many-to-many
     *   column="participant_id"
     *   class="gp.hibxdoc.ParticipantImpl"
     * @hibernate.element
     *   column="odds"
     *   type="float"
     */
    private Map odds;

I am adding participants to an event and giving each one odds. The Participant is the key in the Map and the odds are values. The relationship between events and participants is many-to-many, and the odds are kept in the many-to-many relationship table. Its structure comes out like this:


+----------------+---------+------+-----+---------+-------+
| Field          | Type    | Null | Key | Default | Extra |
+----------------+---------+------+-----+---------+-------+
| id             | int(11) | NO   | PRI |         |       |
| odds           | float   | YES  |     | NULL    |       |
| participant_id | int(11) | NO   | PRI |         |       |
+----------------+---------+------+-----+---------+-------+

All XDoclet2 tags for Hibernate can be found here - http://xdoclet.codehaus.org/HibernateTags

The Ant build file

Mapping files generation

This generates the mapping files (hbm.xml). The resulting files are generated in the src directory as I regard these as an integral part of the source code.


<!-- mapping generation -->       
<target name="generate.hibernate" depends="erase.hibernate.mappings">

    <xdoclet>

        <fileset dir="${basedir}/src">
        <include name="**/*.java" />
        </fileset>

        <component classname="org.xdoclet.plugin.hibernate.HibernateMappingPlugin"
           destdir="${hib.out.dir}"
           version="3.0"
            validate="false">
        </component>

    </xdoclet>    
</target>

Exporting the database

HibernateTool allows you to generate your development DB on the spot. You can also generate DDL, which can be used as a base for the real database.


    <target name="build.database" depends="generate.hibernate">
        <hibernatetool destdir="${db.out.dir}">
            <classpath>
                <path location="${hib.out.dir}" />
                <path refid="jdbc.classpath"/>
                <path location="${basedir}/bin"/>
            </classpath>
            <configuration configurationfile="${hib.out.dir}/hibernate.cfg.xml" />
            <hbm2ddl 
                outputfilename="db.ddl"
                drop="true"
                create="true"
                delimiter=";" 
                console="true"
                format="true" 
                >
            </hbm2ddl>
        </hibernatetool>
    </target>

To make the build independent on the environment, move all the properties in a separate property file.

Hibernate in-memory test DB and the tests

The tests can be run against the in-memory HSQL database. Doing this has several advantages - 1) There’s no need for a DB server to be accessible by the build server. 2) It allows to completely abstract any database concerns and test business logic without actually having a database; 3) They’ll run faster. An obvious drawback is that it does not guarantee some oddity in your actual DB server won’t break it, but hey, these are unit tests, after all.

It is very easy to make Hibernate use another configuration - in this case, I pass the configuration as a parameter to the Hibernate session manager and hold one static instance of each factory in a map keyed on the configuration itself.

HSQLDB must be downloaded from here - http://sourceforge.net/project/showfiles.php?group_id=23316 (version 1.8.0).

Then, put it on your classpath, make a test config, e.g. copy the real one, but replace with the hsql properties:


...
<property name="dialect">org.hibernate.dialect.HSQLDialect</property>
<property name="show_sql">false</property>
<property name="use_outer_join">false</property>
    <property name="connection.username">sa</property>
    <property name="connection.password"></property>
    <property name="connection.driver_class">org.hsqldb.jdbcDriver</property>
    <property name="connection.url">jdbc:hsqldb:mem:baseball</property>
    <property name="connection.pool_size">1</property>
    <property name="current_session_context_class">thread</property>
<property name="show_sql">true</property>
<property name="cache.provider_class">
    org.hibernate.cache.HashtableCacheProvider
</property>

<property name="hbm2ddl.auto">create-drop</property>
... 

Problems

XDoclet can validate the produced XML. Well I say can, but it can’t. It’s trying to find this schema and failing - http://apache.org/xml/features/validation/schema. And I don’t blame it, because it’s not there, therefore I switch off the validation in the build file (validate=”false”)

Failng to validate the mappings as they are generated may result in errors occuring later in the process, e.g. when generating the database. Unfortunately Hibernate tool may be a bit frugal with the details. To get the max out of it bump up the logging details:

log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n
log4j.rootLogger=trace, stdout

Another way to get more info is to run ant with -v (verbose) to see even more details

Some of the errors quote directly jboss, which again I believe violates the RRE principle

If the generated DDL is to be used as a base for the real DB, then you’d like to be very thorough and remove every hint of automatic configuration from it. For example the Hibernate “convention” for foreign keys, resulting in something like “FK398748273687” tends to piss off DBAs like nothing..

So what’s this good for

This approach allows to easily abstract all persistence concerns from the design and development process. You can focus on the model and not worry too much. Later, the automatically generated DB can be replaced, but that is rarely the case. Ususally people will be quite content with something that works well, so you end up with a really cheap persistence solution. The determining factor is probably the quality of the mappings. Bad mappings will get you into trouble. So make sure you peruse the Hibernate documentation.

Comments

  1. Jacob B. said 40 days later:
    Excellent article. The work I just got handed deals a lot with POJO-DB work, throw the requirement for XML data import/export (which Hibernate is currently at an experimental stage with). This work will go a lot easier with the things I learned from your article, particularly, generating DDL and hbm.xml files for storing POJOs in an RDBMS. Thanks, you're saving me a ton of mind-numbing work!

(leave url/email »)

  

Home

Who's George?

Recent entries