Tuesday, April 10, 2007

First Hibernate Application

For the beginning, we will do a very simple console based Hibernate Application.
We will use an in-memory database, so we do not have to install any server stuff.
Feedback about this tutorial appreciated via uchiha_didik@yahoo.com


Let's assume we want to have a small application where we can store events
we want to attend and who hosts these events. We decide we want to use Hibernate
for storage, because we heard it is the coolest thing in persistence :)


So the first thing we will do is set up our development directory and put all
the jar-Files we need in it. We have to download the Hibernate distribution
from the Hibernate download
page
. Extract the needed jars from the hibernate archive. We will place
them all in a lib directory under the development dir, so your devel directory
should now look like this:


.
+lib
antlr-2.7.4.jar
cglib-full-2.0.2.jar
commons-collections-2.1.1.jar
commons-logging-1.0.4.jar
hibernate3.jar
jta.jar
dom4j-1.5.2.jar
jdbc2_0-stdext.jar
log4j-1.2.9.jar

The first class


So the next thing we will do is create a class which represents the events
we want to store. This will be just a simple Java Bean, which contains some
properties. Let's look at the code:


package de.gloegl.road2hibernate;

import java.util.Date;

public class Event {
private String title;
private Date date;
private Long id;

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;
}
}

Some things are noteworthy here:


The id property is a unique id for the Event - all our persistent objects will
need such an id. A good idea when building hibernate applications is to keep
such unique ids separate from the application logic. This means we will not
manipulate the id anywhere in our code, and leave it to Hibernate to care about
it. That's why the setter of the id is private, allowing Hibernate to use them
(Hibernate may access property get- and set-Methods of all visibilities), but
shielding it from us.


We are also using a true Long for the id, not a primitive type long. This will
save us a lot of hassles later on - so always use Objects for the id property,
never primitive types (If it is possible).


We will place this file in a directory called src in our development folder.
The directory should now look like this:


.
+lib
<here are the jar files>
+src
+de
+gloegl
+road2hibernate
Event.java

The mapping file


As we now have our class to store in the database, we must of course tell hibernate
how to persist it. This is where the mapping file comes into play. The mapping
file tells Hibernate what should stored in the database - and how.


The outer 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>

Between the two <hibernate-mapping> tags, we will include a
class element, where we can declare which class this mapping refers to and to
which table in our SQL database the class should be mapped. So step 2 of our
mapping document 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>

<class name="de.gloegl.road2hibernate.Event" table="EVENTS">

</class>

</hibernate-mapping>

So what we have done so far is telling hibernate to persist our class Event
to the table EVENTS. No we will have to give hibernate the property to use as
unique identifier - which is what we have included the id property for. In addition,
as we don't want to care about handling this id value, we have to tell hibernate
how to generate the ids. Including this, our 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>

<class name="de.gloegl.road2hibernate.Event" table="EVENTS">
<id name="id" column="uid" type="long">
<generator class="increment"/>
</id>
</class>

</hibernate-mapping>

So what does all this mean? The <id> element is the declaration
of the id property. name="id" is the name of the property - hibernate will use
getId and setId to access it. The column attribute tells hibernate which column
of the EVENT table will contain the id. The type attribute tells hibernate about
the property type - in this case a long. For now the type attribute is not important
to us, if you want to know more about it see Hibernate
Documentation
(Section 5.2)


The <generator> element specifies the id generation technique
to use - in this case we will use increment, which is a very simple generation
method, but which we will use in our small example as it is sufficient here.


Finally we have to include declarations for the persistent properties in the
mapping file:


<?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="de.gloegl.road2hibernate.Event" table="EVENTS">
<id name="id" column="uid" type="long">
<generator class="increment"/>
</id>
<property name="date" type="timestamp"/>
<property name="title" column="eventtitle"/>
</class>

</hibernate-mapping>

There are a few things noteworthy here. At first, just as with the <id>
element, the name attribute of the <property> element tells Hibernate
which get- and set-methods to use.


However, you will notice that the title-property contains a column attribute,
however the date attribute does not. This is possible because when the column
attribute is left out, Hibernate by default uses the property name as column
name.


The next interesting thing is that the title-property lacks a type attribute.
Again, Hibernate will try to determine the correct type itself if the type attribute
is left out. Sometimes however Hibernate just can't do that and we have to specify
the type - as it is the case with the date property. Hibernate can't know if
the property will map to a date, timestamp or time column in the database, so
we have to specify this.


We will place the mapping in a file called Event.hbm.xml right in the directory
where our Event class is located. So your directory structure should now look
like this:


.
+lib
<here are the jar files>
+src
+de
+gloegl
+road2hibernate
Event.java
Event.hbm.xml

Configuration and Database


As we now have our persistent class and the mapping file it is time to configure
Hibernate. Before we do this, we will need a database however, so we go on and
get HSQLDB, a java-based in-memory SQL Database, from the HSQLDB
website
. What we need is the hsqldb.jar from the lib directory of the zip
download. We will place it in our lib directory which should now look like this:


.
+lib
<hibernate jars>
hsqldb.jar
+src
<source and mapping files are here>

In addition, we will create a directory data right under our development directory,
where hsqldb will store its files.


Now Hibernate configuration can be done in an xml file, which we will call
hibernate.cfg.xml and place it directly in the src folder of our development
directory. This file looks like this:


<?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>
<property name="hibernate.connection.driver_class">org.hsqldb.jdbcDriver</property>
<property name="hibernate.connection.url">jdbc:hsqldb:data/test</property>
<property name="hibernate.connection.username">sa</property>
<property name="hibernate.connection.password"></property>
<property name="dialect">org.hibernate.dialect.HSQLDialect</property>
<property name="show_sql">true</property>
<property name="transaction.factory_class">
org.hibernate.transaction.JDBCTransactionFactory
</property>
<property name="hibernate.cache.provider_class">
org.hibernate.cache.HashtableCacheProvider
</property>
<property name="hibernate.hbm2ddl.auto">update</property>

<mapping resource="de/gloegl/road2hibernate/Event.hbm.xml"/>

</session-factory>

</hibernate-configuration>

The first four <property> elements contain the necessary configuration
for the JDBC-Connection Hibernate will use. The dialect <property>
element specifies the SQLdialect Hibernate shall generate. Next we specify that
Hibernate shall delegate transactions to the underlying JDBC connection and
specify a simple cache provider (this is without effect because we don't use
any caching yet). The next property tells hibernate to automatically adjust
the tables in the database according to our mappings. Finally we give the path
to our mapping file.


Building


So finally we can start building our first application. For convenience, we
create a batch file in our development directory which contains all commands
necessary for compilation. Under Windows, this would look like this:


javac -classpath .\lib\hibernate3.jar -d bin src\de\gloegl\road2hibernate\*.java
copy /Y src\hibernate.cfg.xml bin
copy /Y src\de\gloegl\road2hibernate\*.xml bin\de\gloegl\road2hibernate

We place this file called build.bat in our development directory. If you are
using Linux, you can surely create an equivalent shell script :)


Finally we create the bin subdirectory of our development directory to place
the compiled classes in.


Running


So now we will create a simple class which will start up Hibernate. It looks
like this:


package de.gloegl.road2hibernate;

import org.hibernate.SessionFactory;
import org.hibernate.HibernateException;
import org.hibernate.cfg.Configuration;

public class EventManager {

public static void main(String[] args) {
Session s = HibernateUtil.currentSession();
HibernateUtil.closeSession();
System.exit(0);
}

}

This class just uses a class called HibernateUtil to get a Session instance.
HibernateUtil is where all the magic goes on. It uses the so called "ThreadLocal
Session Pattern" to keep the current session associated with the current thread.
Lets have a look at it:


package de.gloegl.road2hibernate;

import org.hibernate.*;
import org.hibernate.cfg.*;

public class HibernateUtil {

private static final SessionFactory sessionFactory;

static {
try {
// Create the SessionFactory
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 final ThreadLocal session = new ThreadLocal();

public static Session currentSession() throws HibernateException {
Session s = (Session) session.get();
// Open a new Session, if this Thread has none yet
if (s == null) {
s = sessionFactory.openSession();
session.set(s);
}
return s;
}

public static void closeSession() throws HibernateException {
Session s = (Session) session.get();
session.set(null);
if (s != null)
s.close();
}
}

This class does not only create the SessionFactory in its static initializer,
but also has a ThreadLocal variable which holds the Session for the current
thread. Make sure you understand the Java concept of a thread-local variable
before you try to use this helper. A more complex and powerful HibernateUtil
class can be found in CaveatEmptor


Place both the EventManager.java and the HibernateUtil.java in the directory
where Event.java already is:


.
+lib
antlr-2.7.4.jar
cglib-full-2.0.2.jar
commons-collections-2.1.1.jar
commons-logging-1.0.4.jar
hibernate3.jar
jta.jar
dom4j-1.5.2.jar
jdbc2_0-stdext.jar
log4j-1.2.9.jar
hsqldb.jar
+src
+de
+gloegl
+road2hibernate
Event.java
Event.hbm.xml
EventManager.java
HibernateUtil.java
hibernate.cfg.xml
+data
build.bat

Now compile everything by starting build.bat in the development directory.
Run the application by running this in the development directory (all in one
line):


java -classpath .\lib\hibernate3.jar;.\lib\log4j-1.2.9.jar;.\lib\jta.jar;
.\lib\antlr-2.7.4.jar.\lib\commons-logging-1.0.4.jar;.\lib\hsqldb.jar;
.\lib\cglib-full-2.0.2.jar;.\lib\commons-collections-2.1.1.jar;
.\lib\dom4j-1.5.2.jar;.\lib\jdbc2_0-stdext.jar;
.\bin de.gloegl.road2hibernate.EventManager

This should produce the following output:


Initializing Hibernate
log4j:WARN No appenders could be found for logger (org.hibernate.cfg.Environment).
log4j:WARN Please initialize the log4j system properly.
Finished Initializing Hibernate

Let's place this line in a batch file too, so we can run it a little more conveniently.
Place it in the development directory, called run.bat (add %1 %2 %3 %4 %5 at
the end of the line).


So we are happy that we don't have any errors so far - but we still want to
see what Hibernate is doing during startup and want to get rid of those warnings,
so we have to configure log4j. This is done by putting the following file called
log4j.properties in your src-Directory:


log4j.rootCategory=INFO, A1
log4j.appender.A1=org.apache.log4j.ConsoleAppender
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=%-5p - %m%n

In addition, the following line has to be added to the build.bat:


copy /Y src\log4j.properties bin

We won't get into what exactly this all is, in effect it tells log4j to write
all log output to the console.


Now recompile the application by starting build.bat again, and rerun it - now
there should be detailed info what hibernate is doing in your output.


Working with persistence


As we now finally have configured hibernate and our mappings, we take advantage
of hibernate and persist some objects. We will adjust our EventManager class
now to do some work with hibernate.


At first we modify the main-Method:


public static void main(String[] args) throws java.text.ParseException {
EventManager instance = new EventManager();
if (args[0].equals("store")) {
String title = args[1];
Date theDate = new Date();
instance.store(title, theDate);
}
System.exit(0);
}

So what we do here is read some arguments from the command line, and if the
first argument to our application is store, we take the second argument as the
title, create a new Date and pass both to the store method, where it
gets really interesting:


private void store(String title, Date theDate) {        
try {
Session session = HibernateUtil.currentSession();
Transaction tx = session.beginTransaction();

Event theEvent = new Event();
theEvent.setTitle(title);
theEvent.setDate(theDate);

session.save(theEvent);

tx.commit();
hsqlCleanup(session);
HibernateUtil.closeSession();
} catch (HibernateException e) {
e.printStackTrace();
}
}

Well, isn't that cool? We simply create a new Event object, and hand it over
to Hibernate. Hibernate now takes care of creating the SQL, and sending it to
the database. We can even start and stop transactions, which Hibernate will
delegate to the JDBC Connection.


The method hsqlCleanup listed below performs some necessary shutdown code telling
HsqlDB to remove all its lock-files and flush its logs. This is not a direct
hibernate requirement.


private void hsqlCleanup(Session s) {
try {
s.connection().createStatement().execute("SHUTDOWN");
} catch (Exception e) {
}
}

Please not that all the transaction and session handling code in these
examples is extremely unclean, mainly for shortness. Please don't use that in
a production app.


If we now run the application with run.bat store Party an Event object
will be created an persisted to the database.


But now we want to list our stored Events, so we modify the main Method some
more:


public static void main(String[] args) throws java.text.ParseException {
EventManager instance = new EventManager();
if (args[0].equals("store")) {
String title = args[1];
Date theDate = new Date();
instance.store(title, theDate);
} else if (args[0].equals("list")) {
List events = instance.listEvents();
for (int i = 0; i<events.size(); i++) {
Event theEvent = (Event) events.get(i);
System.out.println(
"Event " + theEvent.getTitle() + " Time: " + theEvent.getDate());
}
}
System.exit(0);
}

When the first argument is "list", we call listEvents() and print
all Events contained in the returned list. listEvents() is where the
interesting stuff happens:


private List listEvents() {
try {
Session session = HibernateUtil.currentSession();
Transaction tx = session.beginTransaction();

List result = session.createQuery("from Event").list();

tx.commit();
hsqlCleanup(session);
HibernateUtil.closeSession();

return result;
} catch (HibernateException e) {
throw new RuntimeException(e.getMessage());
}
}

What we do here is using a HQL (Hibernate Query Language) query to load all
existing Events 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 querys with HQL of course, which we will see in later chapters.


So in this chapter we learned how to setup Hibernate, how to create a mapping
for our classes, and how to store and retrieve objects using Hibernate.


That's it for the first chapter, in the next part we will replace our ugly
build.bat with an ant-based build system.


Code download


You can download the part one development directory here

No comments: