Ant + Hibernate = Confusion and Pain
Once again, an attempt to get two stable, mature, well-documented open source Java tools to talk to each other has resulted in several hours of frustration, as well as all the bad karma associated with using Those Words (as my Grade 3 teacher called them) out loud. I finally figured out how to avoid the problem, but I still don't understand why it happens in the first place. If any Ant and/or Hibernate gurus would care to enlighten me, I would be grateful.
Here's what happened:
- Background
- I wanted to create a near-trivial example application to introduce future generations of students to Hibernate (an object/relational mapping framework for Java). Hibernate gives developers two options: write a JavaBean class, then write a mapping file to describe how that class's members map to database tables, or write the mapping file, and then have Hibernate generate the JavaBean class. Half of this fall's students did it one way, half did it the other; based on their experiences, I decided to go the code generation route.
- Setup
- I created four files:
hibernate.cfg.xml
: the Hibernate configuration file.build.xml
: the Ant build file. (If you haven't seen Ant, it's a pure-Java replacement for Make, which has become the standard build tool for Java programming).src/User.hbm.xml
: the Hibernate mapping file for myUser
class.src/Test.java
: a simple test program. I initially used JUnit, but when things started to go pear-shaped [1], I yanked it out in an attempt to isolate the problem.
Note that my source files are in a
src
directory.build.xml
creates two output directories at the start of the build process: one calledgen
, for generated code, and another calledclass
, for compiled.class
files.build.xml
has four targets:clean
: deletes thegen
andclass
directories, and also thedata
directory (described below).codegen
: invokes Hibernate'sHbm2JavaTask
to generategen/User.java
fromsrc/User.hbm.xml
.compile
: compilesgen/User.java
andsrc/Test.java
to createclass/User.class
andclass/Test.class
.schema
: creates adata
directory, and invokes Hibernate'sSchemaExportTask
to generate database schema files in that directory.SchemaExportTask
readssrc/User.hbm.xml
, checks that it's consistent withclass/User.class
(compile-time checking—excellent), and createsdata/hippo.log
anddata/hippo.properties
, which between them tell HSQLDB (the database I'm using) what table(s) to create, and how.
- The first hour and a half
- The examples in Hibernate in Action (the book I'm using) show several examples of
hibernate.properties
files, which use the older Java properties file syntax, but when it comes to XML configuration files, the book says, "Go see your install documentation." I didn't find that documentation helpful; in particular, it wasn't clear when the XML configuration file's properties had to have the same names as were used in the plain text configuration file, and when they had to be different. Hibernate's error messages didn't help at all: all I could do to debug was make more-or-less random changes to the configuration file, and see what effect they had. - The next hour and a half
I finally annealed [2] my configuration file into a working state. A fresh cup of tea, and I was ready to start making some progress—except now Ant was unhappy. The four targets in
build.xml
are linearly dependent:schema
depends oncompile
, whilecompile
depends oncodegen
.ant schema
should therefore erase the working directories, generategen/User.java
, compile it andsrc/Test.java
, and generate a database schema.When I tried it, the first three steps worked perfectly, but schema generation failed with an error message saying, "Unable to read User.class". My first thought was that this was yet another classpath problem, but then I discovered that if I ran
ant schema
again, without runningant clean
in between, Ant and Hibernate would generate my schema for me.After ninety minutes and a lot of bad language, I convinced myself that this was some sort of timing problem. If I ran
ant compile
, then ran the schema-generation target on its own, everything worked. If I left the dependency between theschema
andcompile
targets inbuild.xml
, and tried to do everything in a single Ant run, it failed. I went so far as to revive a little Python inventory tool I wrote a while back, which walked through the directory tree, recording timestamps, file sizes, and MD5 checksums. As far as I could tell, there was no difference in the files generated by running Ant in two steps, versus running it in one.And no, it isn't Windows-specific: I get exactly the same behavior on Debian Linux…
At this point, I know how to work around the problem (run Ant twice), and could go back to working on my Hibernate example. But how am I going to explain this to students? "Hi, everyone, this term we're going to be using Ant. It's a replacement for Make, and sometimes, well, sometimes you just have to run it a couple of times to get it to work." I don't think it's acceptable for software to act like a spoiled six-year-old ("I won't! I won't! I won't I won't I won't and you can't make me!"); I certainly don't feel comfortable telling my students that when it does, they should just put up with it.
If you know something about Ant and Hibernate, and would like to figure out what's going on, I've put the project's four files on the web, and I'm easy to reach. I look forward to hearing from you.
[1] "Pear-shaped" is a British expression meaning "gone bad". No idea how it originated; if anyone knows, I'd like to hear.
[2] "Annealing" is the process of banging on metal to get the kinks out. "Simulated annealing" is an optimization technique in which you make random changes to your proposed solution, keeping those that make things better, and throwing aways those that don't. Simulated annealing is a lousy way to debug programs.