Let's assume we need a small database application that can store events we want to attend, and information about the host(s) of these events. We will use an in-memory, Java database named HSQLDB to avoid describing installation/setup of any particular database servers. Feel free to tweak this tutorial to use whatever database you feel comfortable using.
The first thing we need to do is set up our development environment,
and specifically to setup all the required dependencies to Hibernate
as well as other libraries. Hibernate is built using Maven which
amongst other features provides dependecy management
;
moreover it provides transitive
dependecy management
which simply means that to use
Hibernate we can simply define our dependency on Hibernate, Hibernate
itself defines the dependencies it needs which then become transitive
dependencies of our project.
. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> ... <dependencies> <dependency> <groupId>${groupId}</groupId> <artifactId>hibernate-core</artifactId> </dependency> <!-- Because this is a web app, we also have a dependency on the servlet api. --> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> </dependency> </dependencies> </project>
Essentially we are describing here the
/tutorials/web/pom.xml
file. See the
Maven site for more information.
While not strictly necessary, most IDEs have integration with Maven to read these POM files and automatically set up a project for you which can save lots of time and effort.
Next we create a class that represents the event we want to store in database.
Our first persistent class is a simple JavaBean class with some properties:
package org.hibernate.tutorial.domain; import java.util.Date; public class Event { private Long id; private String title; private Date date; public Event() {} public Long getId() { return id; } private void setId(Long id) { this.id = id; } public Date getDate() { return date; } public void setDate(Date date) { this.date = date; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } }
You can see that this class uses standard JavaBean naming conventions for property getter and setter methods, as well as private visibility for the fields. This is a recommended design - but not required. Hibernate can also access fields directly, the benefit of accessor methods is robustness for refactoring. The no-argument constructor is required to instantiate an object of this class through reflection.
The id
property holds a unique identifier value for a particular event.
All persistent entity classes (there are less important dependent classes as well) will need
such an identifier property if we want to use the full feature set of Hibernate. In fact,
most applications (esp. web applications) need to distinguish objects by identifier, so you
should consider this a feature rather than a limitation. However, we usually don't manipulate
the identity of an object, hence the setter method should be private. Only Hibernate will assign
identifiers when an object is saved. You can see that Hibernate can access public, private,
and protected accessor methods, as well as (public, private, protected) fields directly. The
choice is up to you and you can match it to fit your application design.
The no-argument constructor is a requirement for all persistent classes; Hibernate has to create objects for you, using Java Reflection. The constructor can be private, however, package visibility is required for runtime proxy generation and efficient data retrieval without bytecode instrumentation.
Place this Java source file in a directory called src
in the
development folder, and in its correct package. The directory should now look like this:
. +lib <Hibernate and third-party libraries> +src +events Event.java
In the next step, we tell Hibernate about this persistent class.
Hibernate needs to know how to load and store objects of the persistent class. This is where the Hibernate mapping file comes into play. The mapping file tells Hibernate what table in the database it has to access, and what columns in that table it should use.
The basic structure of a mapping file looks like this:
<?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> [...] </hibernate-mapping>
Note that the Hibernate DTD is very sophisticated. You can use it for
auto-completion of XML mapping elements and attributes in your editor or
IDE. You also should open up the DTD file in your text editor - it's the
easiest way to get an overview of all elements and attributes and to see
the defaults, as well as some comments. Note that Hibernate will not
load the DTD file from the web, but first look it up from the classpath
of the application. The DTD file is included in hibernate3.jar
as well as in the src/
directory of the Hibernate distribution.
We will omit the DTD declaration in future examples to shorten the code. It is of course not optional.
Between the two hibernate-mapping
tags, include a
class
element. All persistent entity classes (again, there
might be dependent classes later on, which are not first-class entities) need
such a mapping, to a table in the SQL database:
<hibernate-mapping> <class name="events.Event" table="EVENTS"> </class> </hibernate-mapping>
So far we told Hibernate how to persist and load object of class Event
to the table EVENTS
, each instance represented by a row in that table.
Now we continue with a mapping of the unique identifier property to the tables primary key.
In addition, as we don't want to care about handling this identifier, we configure Hibernate's
identifier generation strategy for a surrogate primary key column:
<hibernate-mapping> <class name="events.Event" table="EVENTS"> <id name="id" column="EVENT_ID"> <generator class="native"/> </id> </class> </hibernate-mapping>
The id
element is the declaration of the identifier property,
name="id"
declares the name of the Java property -
Hibernate will use the getter and setter methods to access the property.
The column attribute tells Hibernate which column of the
EVENTS
table we use for this primary key. The nested
generator
element specifies the identifier generation strategy,
in this case we used native
, which picks the best strategy depending
on the configured database (dialect). Hibernate supports database generated, globally
unique, as well as application assigned identifiers (or any strategy you have written
an extension for).
Finally we include declarations for the persistent properties of the class in the mapping file. By default, no properties of the class are considered persistent:
<hibernate-mapping> <class name="events.Event" table="EVENTS"> <id name="id" column="EVENT_ID"> <generator class="native"/> </id> <property name="date" type="timestamp" column="EVENT_DATE"/> <property name="title"/> </class> </hibernate-mapping>
Just as with the id
element, the name
attribute of the property
element tells Hibernate which getter
and setter methods to use. So, in this case, Hibernate will look for
getDate()/setDate()
, as well as getTitle()/setTitle()
.
Why does the date
property mapping include the
column
attribute, but the title
doesn't? Without the column
attribute Hibernate
by default uses the property name as the column name. This works fine for
title
. However, date
is a reserved
keyword in most database, so we better map it to a different name.
The next interesting thing is that the title
mapping also lacks
a type
attribute. The types we declare and use in the mapping
files are not, as you might expect, Java data types. They are also not SQL
database types. These types are so called Hibernate mapping types,
converters which can translate from Java to SQL data types and vice versa. Again,
Hibernate will try to determine the correct conversion and mapping type itself if
the type
attribute is not present in the mapping. In some cases this
automatic detection (using Reflection on the Java class) might not have the default you
expect or need. This is the case with the date
property. Hibernate can't
know if the property (which is of java.util.Date
) should map to a
SQL date
, timestamp
, or time
column.
We preserve full date and time information by mapping the property with a
timestamp
converter.
This mapping file should be saved as Event.hbm.xml
, right in
the directory next to the Event
Java class source file.
The naming of mapping files can be arbitrary, however the hbm.xml
suffix is a convention in the Hibernate developer community. The directory structure
should now look like this:
. +lib <Hibernate and third-party libraries> +src +events Event.java Event.hbm.xml
We continue with the main configuration of Hibernate.
We now have a persistent class and its mapping file in place. It is time to configure
Hibernate. Before we do this, we will need a database. HSQL DB, a java-based SQL DBMS,
can be downloaded from the HSQL DB website(http://hsqldb.org/). Actually, you only need the hsqldb.jar
from this download. Place this file in the lib/
directory of the
development folder.
Create a directory called data
in the root of the development directory -
this is where HSQL DB will store its data files. Now start the database by running
java -classpath ../lib/hsqldb.jar org.hsqldb.Server
in this data directory.
You can see it start up and bind to a TCP/IP socket, this is where our application
will connect later. If you want to start with a fresh database during this tutorial,
shutdown HSQL DB (press CTRL + C
in the window), delete all files in the
data/
directory, and start HSQL DB again.
Hibernate is the layer in your application which connects to this database, so it needs connection information. The connections are made through a JDBC connection pool, which we also have to configure. The Hibernate distribution contains several open source JDBC connection pooling tools, but will use the Hibernate built-in connection pool for this tutorial. Note that you have to copy the required library into your classpath and use different connection pooling settings if you want to use a production-quality third party JDBC pooling software.
For Hibernate's configuration, we can use a simple hibernate.properties
file, a
slightly more sophisticated hibernate.cfg.xml
file, or even complete
programmatic setup. Most users prefer the XML configuration file:
<?xml version='1.0' encoding='utf-8'?> <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <session-factory> <!-- Database connection settings --> <property name="connection.driver_class">org.hsqldb.jdbcDriver</property> <property name="connection.url">jdbc:hsqldb:hsql://localhost</property> <property name="connection.username">sa</property> <property name="connection.password"></property> <!-- JDBC connection pool (use the built-in) --> <property name="connection.pool_size">1</property> <!-- SQL dialect --> <property name="dialect">org.hibernate.dialect.HSQLDialect</property> <!-- Enable Hibernate's automatic session context management --> <property name="current_session_context_class">thread</property> <!-- Disable the second-level cache --> <property name="cache.provider_class">org.hibernate.cache.NoCacheProvider</property> <!-- Echo all executed SQL to stdout --> <property name="show_sql">true</property> <!-- Drop and re-create the database schema on startup --> <property name="hbm2ddl.auto">create</property> <mapping resource="events/Event.hbm.xml"/> </session-factory> </hibernate-configuration>
Note that this XML configuration uses a different DTD. We configure
Hibernate's SessionFactory
- a global factory responsible
for a particular database. If you have several databases, use several
<session-factory>
configurations, usually in
several configuration files (for easier startup).
The first four property
elements contain the necessary
configuration for the JDBC connection. The dialect property
element specifies the particular SQL variant Hibernate generates.
Hibernate's automatic session management for persistence contexts will
come in handy as you will soon see.
The hbm2ddl.auto
option turns on automatic generation of
database schemas - directly into the database. This can of course also be turned
off (by removing the config option) or redirected to a file with the help of
the SchemaExport
Ant task. Finally, we add the mapping file(s)
for persistent classes to the configuration.
Copy this file into the source directory, so it will end up in the
root of the classpath. Hibernate automatically looks for a file called
hibernate.cfg.xml
in the root of the classpath, on startup.
We'll now build the tutorial with Ant. You will need to have Ant installed - get
it from the Ant download page.
How to install Ant will not be covered here. Please refer to the
Ant manual. After you
have installed Ant, we can start to create the buildfile. It will be called
build.xml
and placed directly in the development directory.
A basic build file looks like this:
<project name="hibernate-tutorial" default="compile"> <property name="sourcedir" value="${basedir}/src"/> <property name="targetdir" value="${basedir}/bin"/> <property name="librarydir" value="${basedir}/lib"/> <path id="libraries"> <fileset dir="${librarydir}"> <include name="*.jar"/> </fileset> </path> <target name="clean"> <delete dir="${targetdir}"/> <mkdir dir="${targetdir}"/> </target> <target name="compile" depends="clean, copy-resources"> <javac srcdir="${sourcedir}" destdir="${targetdir}" classpathref="libraries"/> </target> <target name="copy-resources"> <copy todir="${targetdir}"> <fileset dir="${sourcedir}"> <exclude name="**/*.java"/> </fileset> </copy> </target> </project>
This will tell Ant to add all files in the lib directory ending with .jar
to the classpath used for compilation. It will also copy all non-Java source files to the
target directory, e.g. configuration and Hibernate mapping files. If you now run Ant, you
should get this output:
C:\hibernateTutorial\>ant Buildfile: build.xml copy-resources: [copy] Copying 2 files to C:\hibernateTutorial\bin compile: [javac] Compiling 1 source file to C:\hibernateTutorial\bin BUILD SUCCESSFUL Total time: 1 second
It's time to load and store some Event
objects, but first
we have to complete the setup with some infrastructure code. We have to startup
Hibernate. This startup includes building a global SessionFactory
object and to store it somewhere for easy access in application code.
A SessionFactory
can open up new Session
's.
A Session
represents a single-threaded unit of work, the
SessionFactory
is a thread-safe global object, instantiated once.
We'll create a HibernateUtil
helper class which takes care
of startup and makes accessing a SessionFactory
convenient.
Let's have a look at the implementation:
package util; import org.hibernate.*; import org.hibernate.cfg.*; public class HibernateUtil { private static final SessionFactory sessionFactory; static { try { // Create the SessionFactory from hibernate.cfg.xml sessionFactory = new Configuration().configure().buildSessionFactory(); } catch (Throwable ex) { // Make sure you log the exception, as it might be swallowed System.err.println("Initial SessionFactory creation failed." + ex); throw new ExceptionInInitializerError(ex); } } public static SessionFactory getSessionFactory() { return sessionFactory; } }
This class does not only produce the global SessionFactory
in
its static initializer (called once by the JVM when the class is loaded), but also
hides the fact that it uses a static singleton. It might as well lookup the
SessionFactory
from JNDI in an application server.
If you give the SessionFactory
a name in your configuration
file, Hibernate will in fact try to bind it to JNDI after it has been built.
To avoid this code completely you could also use JMX deployment and let the
JMX-capable container instantiate and bind a HibernateService
to JNDI. These advanced options are discussed in the Hibernate reference
documentation.
Place HibernateUtil.java
in the development source directory, in
a package next to events
:
. +lib <Hibernate and third-party libraries> +src +events Event.java Event.hbm.xml +util HibernateUtil.java hibernate.cfg.xml +data build.xml
This should again compile without problems. We finally need to configure a logging
system - Hibernate uses commons logging and leaves you the choice between Log4j and
JDK 1.4 logging. Most developers prefer Log4j: copy log4j.properties
from the Hibernate distribution (it's in the etc/
directory) to
your src
directory, next to hibernate.cfg.xml
.
Have a look at the example configuration and change the settings if you like to have
more verbose output. By default, only Hibernate startup message are shown on stdout.
The tutorial infrastructure is complete - and we are ready to do some real work with Hibernate.
Finally, we can use Hibernate to load and store objects. We write an
EventManager
class with a main()
method:
package events; import org.hibernate.Session; import java.util.Date; import util.HibernateUtil; public class EventManager { public static void main(String[] args) { EventManager mgr = new EventManager(); if (args[0].equals("store")) { mgr.createAndStoreEvent("My Event", new Date()); } HibernateUtil.getSessionFactory().close(); } private void createAndStoreEvent(String title, Date theDate) { Session session = HibernateUtil.getSessionFactory().getCurrentSession(); session.beginTransaction(); Event theEvent = new Event(); theEvent.setTitle(title); theEvent.setDate(theDate); session.save(theEvent); session.getTransaction().commit(); } }
We create a new Event
object, and hand it over to Hibernate.
Hibernate now takes care of the SQL and executes INSERT
s
on the database. Let's have a look at the Session
and
Transaction
-handling code before we run this.
A Session
is a single unit of work. For now we'll keep things
simple and assume a one-to-one granularity between a Hibernate Session
and a database transaction. To shield our code from the actual underlying transaction
system (in this case plain JDBC, but it could also run with JTA) we use the
Transaction
API that is available on the Hibernate Session
.
What does sessionFactory.getCurrentSession()
do? First, you can call it
as many times and anywhere you like, once you get hold of your SessionFactory
(easy thanks to HibernateUtil
). The getCurrentSession()
method always returns the "current" unit of work. Remember that we switched the configuration
option for this mechanism to "thread" in hibernate.cfg.xml
? Hence,
the current unit of work is bound to the current Java thread that executes our application.
However, this is not the full picture, you also have to consider scope, when a unit of work
begins and when it ends.
A Session
begins when it is first needed, when the first call to
getCurrentSession()
is made. It is then bound by Hibernate to the current
thread. When the transaction ends, either through commit or rollback, Hibernate automatically
unbinds the Session
from the thread and closes it for you. If you call
getCurrentSession()
again, you get a new Session
and can
start a new unit of work. This thread-bound programming model is the most
popular way of using Hibernate, as it allows flexible layering of your code (transaction
demarcation code can be separated from data access code, we'll do this later in this tutorial).
Related to the unit of work scope, should the Hibernate Session
be used to
execute one or several database operations? The above example uses one Session
for one operation. This is pure coincidence, the example is just not complex enough to show any
other approach. The scope of a Hibernate Session
is flexible but you should
never design your application to use a new Hibernate Session
for
every database operation. So even if you see it a few more times in
the following (very trivial) examples, consider session-per-operation
an anti-pattern. A real (web) application is shown later in this tutorial.
Have a look at Chapter 11, Transactions And Concurrency for more information about transaction handling and demarcation. We also skipped any error handling and rollback in the previous example.
To run this first routine we have to add a callable target to the Ant build file:
<target name="run" depends="compile"> <java fork="true" classname="events.EventManager" classpathref="libraries"> <classpath path="${targetdir}"/> <arg value="${action}"/> </java> </target>
The value of the action
argument is set on the command line when
calling the target:
C:\hibernateTutorial\>ant run -Daction=store
You should see, after compilation, Hibernate starting up and, depending on your configuration, lots of log output. At the end you will find the following line:
[java] Hibernate: insert into EVENTS (EVENT_DATE, title, EVENT_ID) values (?, ?, ?)
This is the INSERT
executed by Hibernate, the question marks
represent JDBC bind parameters. To see the values bound as arguments, or to reduce
the verbosity of the log, check your log4j.properties
.
Now we'd like to list stored events as well, so we add an option to the main method:
if (args[0].equals("store")) { mgr.createAndStoreEvent("My Event", new Date()); } else if (args[0].equals("list")) { List events = mgr.listEvents(); for (int i = 0; i < events.size(); i++) { Event theEvent = (Event) events.get(i); System.out.println("Event: " + theEvent.getTitle() + " Time: " + theEvent.getDate()); } }
We also add a new listEvents() method
:
private List listEvents() { Session session = HibernateUtil.getSessionFactory().getCurrentSession(); session.beginTransaction(); List result = session.createQuery("from Event").list(); session.getTransaction().commit(); return result; }
What we do here is use an HQL (Hibernate Query Language) query to load all existing
Event
objects from the database. Hibernate will generate the
appropriate SQL, send it to the database and populate Event
objects
with the data. You can create more complex queries with HQL, of course.
Now, to execute and test all of this, follow these steps:
Run ant run -Daction=store
to store something into the database
and, of course, to generate the database schema before through hbm2ddl.
Now disable hbm2ddl by commenting out the property in your hibernate.cfg.xml
file. Usually you only leave it turned on in continuous unit testing, but another
run of hbm2ddl would drop everything you have stored - the
create
configuration setting actually translates into "drop all
tables from the schema, then re-create all tables, when the SessionFactory is build".
If you now call Ant with -Daction=list
, you should see the events
you have stored so far. You can of course also call the store
action a few
times more.
Note: Most new Hibernate users fail at this point and we see questions about Table not found error messages regularly. However, if you follow the steps outlined above you will not have this problem, as hbm2ddl creates the database schema on the first run, and subsequent application restarts will use this schema. If you change the mapping and/or database schema, you have to re-enable hbm2ddl once again.