So you want to learn Hibernate

Me too. Unfortunately, I’m finding the learning curve just a bit steep – or maybe it’s just that my brain is not wired the right way. I like to start with the simplest example that does something and then start playing from there. The examples in the Hibernate documentation weren’t quite what I was looking for. For one thing, they all seemed to run inside an App Server. So here is my version of a simple Hibernate application.

Let’s start with some book keeping. I’m using Java 1.4.1_01 on Mac OS X, Ant 1.5.1, Hibernate 2.0.1, and HSqlDB 1.7.1. I chose HSqlDB because I would like to run an in-process database at some point. Although I haven’t tried any other databases, I would expect good results with any other database known to Hibernate. And I would also expect good results with closely related versions of Java and Ant. If you are in a hurry to play, then you can grab the example distribution and go to town (but see this warning first).

Now, on to my Ant build file.

  • It starts with two properties that specify the location of the hibernate distribution and the jdbc driver – these should be the only properties that you need to change for your system.
  • The directory hierarchy consists of 3 directories: src-dir for Java source, obj-dir for compiled classes, and cfg-dir for all configuration files.
  • There are two defined classpaths. The base classpath includes our class files, all the hibernate jar files (I’m not positive that we need them all, but they must be in there for a reason), and the JDBC jar file. And the execution classpath adds our configuration directory in addition to that.
  • Finally, be sure to set fork=”true” in your java task; this prevents xml parser conflicts by isolating the hibernate application from the ant application.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
<project default="all">
<property name="HBM-HOME" location="/Projects/Java/hibernate" />
<property name="JDBC" location="/Projects/Server/hsqldb/lib/hsqldb.jar" />

<property name="src-dir" location="src" />
<property name="cfg-dir" location="cfg" />
<property name="obj-dir" location="obj" />
<property name="TALK" value="false" />

<path id="classpath.base">
<pathelement location="${obj-dir}" />
<pathelement location="${HBM-HOME}/hibernate2.jar" />
<fileset dir="${HBM-HOME}/lib" includes="**/*.jar" />
<pathelement location="${JDBC}" />
</path>
<path id="classpath.run">
<pathelement location="${cfg-dir}" />
<path refid="classpath.base" />
</path>

<target name="init">
<mkdir dir="${obj-dir}" />
</target>

<target name="compile" depends="init">
<javac srcdir="${src-dir}"
destdir="${obj-dir}"
>

<classpath refid="classpath.base" />
</javac>
</target>
<target name="clean-compile">
<delete verbose="${TALK}" dir="${obj-dir}" />
</target>

<target name="run" depends="compile">
<java classname="Main"
fork="true">

<classpath refid="classpath.run" />
</java>
</target>

<target name="dist">
<jar destfile="distrib.jar"
basedir="."
includes="build.xml,src/**,cfg/**"
excludes="src/prj.el"
/>

</target>
<target name="clean-dist">
<delete VERBOSE="${TALK}" file="distrib.jar"/>
</target>

<target name="all" depends="run" />
<target name="clean" depends="clean-compile,clean-dist" />

</project>

<!-- $Id: build.xml,v 1.6 2003/07/07 19:29:01 dwight Exp $ -->

And here is where all the action is. We start with a Configuration object that specifies how Hibernate will interact with the underlying Database and add a mapping for the hb.Keyword.class (hibernate will load the file hb/Keyword.hbm.xml from the CLASSPATH). Then we create the Session that acts as a service for persistent objects (via a SessionFactory). Finally, we create a Transaction and save a Keyword object via the Session object.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
//
// $Id: Main.java,v 1.3 2003/07/07 19:09:33 dwight Exp $
//
import net.sf.hibernate.cfg.Configuration;
import net.sf.hibernate.SessionFactory;
import net.sf.hibernate.Session;
import net.sf.hibernate.Transaction;
import net.sf.hibernate.MappingException;
import net.sf.hibernate.HibernateException;

import hb.*;


public class Main {

public static void main ( String[] args )
throws MappingException,
HibernateException
{

Configuration cfg = new Configuration();
cfg.addClass( hb.Keyword.class );

SessionFactory sessions = cfg.buildSessionFactory();
Session session = sessions.openSession();

Transaction tx = null;
Keyword kw = new Keyword( 1,"red" );
try {
tx = session.beginTransaction();
session.save( kw );
tx.commit();
}
catch ( HibernateException he ) {
if ( tx != null ) tx.rollback();
throw he;
}
finally {
session.close();
}
}
}

Now let’s take a look at our configuration files. First, the hibernate.properties file. This specifies that we’ll be using HSqlDB and how the application should connect to HSqlDB server instance.

1
2
3
4
5
6
7
8
9
10
11
12
#
#
hibernate.connection.driver_class=org.hsqldb.jdbcDriver
hibernate.connection.url=jdbc:hsqldb:hsql:\//localhost
hibernate.connection.username=user
hibernate.connection.password=user
hibernate.connection.pool_size=2
#
hibernate.dialect=net.sf.hibernate.dialect.HSQLDialect
#
# $Id: hibernate.properties,v 1.1 2003/07/05 21:39:57 dwight Exp $
#

And finally, the mapping from the hb/Keyword class to the KEYWORDS database table (I’m skipping the Keyword.java file because it’s mostly boilerplate: 2 instance variables, 2 constructors, 2 pairs of get/set methods. Hibernate requires accessors and mutators for persistent fields and a default constructor). The use of the identity generator requires that the KEYWORDS.id column be defined as type identity – you’ll need to look at Hibernate Identity Columns and Sequences to use a database other than HSqlDB.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 2.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">


<hibernate-mapping>
<!-- table created by: CREATE TABLE KEYWORDS ( ID IDENTITY, NAME VARCHAR(25) ); -->

<class name="hb.Keyword"
table="keywords">


<id name="id"
type="integer"
column="id">

<generator class="identity"/>
</id>

<property name="name"
column="NAME"
not-null="true"
unique="true"
/>

</class>

</hibernate-mapping>

<!-- $Id: Keyword.hbm.xml,v 1.3 2003/07/08 19:18:30 dwight Exp $ -->

Warning: this application persists keyword objects without regard for any existing persistent keyword objects. Consequently, another row will be added to the database with each execution. We’ll take a look at fixing that later.

With that warning out of the way, you’re ready to get the example distribution and go to town. Here is the output from a sample run on my system (somewhat modified for brevity and format):

1
$ ant
Buildfile: build.xml

init:

compile:

run:
     [java] Jul 9, 2003 7:42:38 AM net.sf. ... .Environment <clinit>

     [java] INFO: Hibernate 2.0.1
     [java] Jul 9, 2003 7:42:38 AM net.sf. ... .Environment <clinit>
     [java] INFO: loaded properties from resource hibernate.properties:
                                  { ... }
     [java] Jul 9, 2003 7:42:38 AM net.sf. ... .Environment <clinit>
     [java] INFO: using CGLIB reflection optimizer
     [java] Jul 9, 2003 7:42:38 AM net.sf. ... .Environment <clinit>
     [java] INFO: JVM proxy support: true
     [java] Jul 9, 2003 7:42:38 AM net.sf. ... .Configuration addClass
     [java] INFO: Mapping resource: hb/Keyword.hbm.xml
     [java] Jul 9, 2003 7:42:41 AM net.sf. ... .Binder bindRootClass
     [java] INFO: Mapping class: hb.Keyword -> keywords
     [java] Jul 9, 2003 7:42:42 AM net.sf. ... .SessionFactoryImpl <init>

     [java] INFO: building session factory
     [java] Jul 9, 2003 7:42:43 AM net.sf. ... .Dialect <init>
     [java] INFO: Using dialect: net.sf.hibernate.dialect.HSQLDialect
     [java] Jul 9, 2003 7:42:43 AM net.sf. ...
                                  .DriverManagerConnectionProvider configure
     [java] INFO: Hibernate connection pool size: 2
     [java] Jul 9, 2003 7:42:43 AM net.sf. ...
                                  .DriverManagerConnectionProvider configure
     [java] INFO: using driver: org.hsqldb.jdbcDriver at URL:
                                  jdbc:hsqldb:hsql:\//localhost
     [java] Jul 9, 2003 7:42:43 AM net.sf. ...
                                  .DriverManagerConnectionProvider configure
     [java] INFO: connection properties: {user=user, password=user}
     [java] Jul 9, 2003 7:42:43 AM net.sf. ... .SessionFactoryImpl <init>
     [java] INFO: Use outer join fetching: false
     [java] Jul 9, 2003 7:42:43 AM net.sf. ... .SessionFactoryImpl <init>
     [java] INFO: Use scrollable result sets: true
     [java] Jul 9, 2003 7:42:46 AM net.sf. ...
                                  .SessionFactoryObjectFactory addInstance
     [java] INFO: no JDNI name configured
     [java] Jul 9, 2003 7:42:46 AM net.sf. ... .SessionFactoryImpl <init>
     [java] INFO: Query language substitutions: {}

all:

BUILD SUCCESSFUL
Total time: 18 seconds

Now that we’re here, I have to say that getting started with Hibernate wasn’t so bad. It just wasn’t obvious that it would turn out to be this simple.

Disclaimer: I don’t claim to be an expert on hibernate. Please send comments and corrections.