Sharky's rewrite of the Hibernate tutorial: part 1: your first Hibernate application

From Sharkysoft Wiki
Jump to: navigation, search

This is a continuation of Sharky's rewrite of the Hibernate tutorial.

Contents

part 1: your first Hibernate application

For this example, we will set up a small database application that can store events we want to attend and information about the hosts of these events.

note

Although you can use whatever database you feel comfortable using, we will use HSQLDB (an in-memory Java database) to avoid describing installation and setup of any particular database servers.

setup

The first thing we need to do is to set up the development environment. We will be using the "standard layout" advocated by many build tools, such as Maven. Maven, in particular, has a good resource describing this layout. As this tutorial is to be a web application, we will be creating and making use of the following folders:

  • src/main/java
  • src/main/resources
  • src/main/webapp

We will be using Maven in this tutorial, taking advantage of its transitive dependency management capabilities, as well as the ability of many IDEs to automatically set up a project for us based on the Maven descriptor, pom.xml.

Before continuing, make sure that Maven is installed. (Instructions are not given here, but there are many resources that can help you.)

Next, create a new folder, anywhere you like, called "hibernate-tutorial". In that folder, create the folder subtrees listed above, and then create a file called pom.xml, with the following contents:

<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"
>
	<modelVersion>4.0.0</modelVersion>
	<groupId>org.hibernate.tutorials</groupId>
	<artifactId>hibernate-tutorial</artifactId>
	<version>1.0-SNAPSHOT</version>
	<packaging>jar</packaging>
	<name>First Hibernate Tutorial</name>
	<build>
		<finalName>${artifactId}</finalName>
		<pluginManagement>
			<plugins>
				<plugin>
					<groupId>org.apache.maven.plugins</groupId>
					<artifactId>maven-compiler-plugin</artifactId>
					<configuration>
						<source>1.5</source>
						<target>1.5</target>
					</configuration>
				</plugin>
			</plugins>
		</pluginManagement>
	</build>
	<dependencies>
		<dependency>
			<groupId>org.hibernate</groupId>
			<artifactId>hibernate-core</artifactId>
			<version>3.3.2.GA</version>
		</dependency>		
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>servlet-api</artifactId>
			<version>2.5</version>
		</dependency>
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-simple</artifactId>
			<version>1.5.8</version>
		</dependency>
		<dependency>
			<groupId>org.javassist</groupId>
			<artifactId>javassist</artifactId>
			<version>3.14.0-GA</version>
		</dependency>
		<dependency>
			<groupId>org.hsqldb</groupId>
			<artifactId>hsqldb</artifactId>
			<version>1.8.0.10</version>
		</dependency>
	</dependencies>
</project>

tip

It is not a requirement to use Maven. If you wish to use something else to build this tutoial (such as Ant), the layout will remain the same. The only change is that you will need to manually account for all the needed dependencies. If you use something like Ivy, providing transitive dependency management, you would still use the dependencies mentioned below. Otherwise, you'd need to grab all dependencies, both explicit and transitive, and add them to the project's classpath. If working from the Hibernate distribution bundle, this would mean hibernate3.jar, all artifacts in the lib/required folder, and all files from either lib/bytecode/cglib or lib/bytecode/javassist; additionally you will need both the servlet-api jar and one of the slf4j logging backends.

your first Hibernate class

Next, we create a class that represents the event we want to store in the database; it is a simple JavaBean class with some properties.

Create a new file at

src/main/java/org/hibernate/tutorial/domain/Event.java

Include the following contents:

package org.hibernate.tutorial.domain;

import java.util.Date;

public class Event
{

	private Long mlId;
	
	private String msTitle;
	
	private Date mpDate;
	
	public Event()
	{
	}
	
	public Long getId()
	{
		return mlId;
	}
	
	private void setId(Long ilId)
	{
		mlId = ilId;
	}
	
	public Date getDate()
	{
		return mpDate;
	}
	
	public void setDate(Date ipDate)
	{
		mpDate = ipDate;
	}
	
	public String getTitle()
	{
		return msTitle;
	}
	
	public void setTitle(String isTitle)
	{
		msTitle = isTitle;
	}

}

This class uses standard JavaBean naming conventions for property getter and setter methods, as well as private visibility for the fields. Although this is the recommended design, it is not required. Hibernate can also access fields directly, the benefit of accessor methods is robustness for refactoring.

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, especially web applications, need to distinguish objects by identifier, so you should consider this a feature rather than a limitation. However, we usually do not manipulate the identity of an object, hence the setter method should be private. Only Hibernate will assign identifiers when an object is saved. Hibernate can access public, private, and protected accessor methods, as well as public, private and 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 or public visibility is required for runtime proxy generation and efficient data retrieval without bytecode instrumentation.

the mapping file

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.

Create a new XML file at

src/main/resources/org/hibernate/tutorial/domain/Event.hbm.xml

and enter the following text:

<?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 package="org.hibernate.tutorial.domain">
	...
</hibernate-mapping>

This is the basic structure of a mapping file.  The Hibernate DTD is sophisticated. You can use it for auto-completion of XML mapping elements and attributes in your editor or IDE. Opening up the DTD file in your text editor is the easiest way to get an overview of all elements and attributes, and to view the defaults, as well as some comments. 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 hibernate-core.jar (it is also included in the hibernate3.jar, if using the distribution bundle).

important

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 a mapping to a table in the SQL database:

<hibernate-mapping package="org.hibernate.tutorial.domain">
	<class name="Event" table="EVENTS">
		...
	</class>
</hibernate-mapping>

So far we have told Hibernate how to persist and load object of class Event to the table EVENTS. Each instance is now represented by a row in that table. Now we can continue by mapping the unique identifier property to the tables primary key. As we do not want to care about handling this identifier, we configure Hibernate's identifier generation strategy for a surrogate primary key column:

<hibernate-mapping package="org.hibernate.tutorial.domain">
	<class name="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. The name="id" mapping attribute declares the name of the JavaBean property and tells Hibernate to use the getId() and setId() methods to access the property. The column attribute tells Hibernate which column of the EVENTS table holds the primary key value.

The nested generator element specifies the identifier generation strategy (aka how are identifier values generated?). In this case we choose native, which offers a level of portability depending on the configured database dialect. Hibernate supports database generated, globally unique, as well as application assigned, identifiers. Identifier value generation is also one of Hibernate's many extension points and you can plugin in your own strategy.

tip

native is no longer consider the best strategy in terms of portability. for further discussion, see Section 25.4, “Identifier generation.”

Lastly, we need to tell Hibernate about the remaining entity class properties. By default, no properties of the class are considered persistent:

<hibernate-mapping package="org.hibernate.tutorial.domain">
	<class name="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>

Similar to the id element, the name attribute of the property element tells Hibernate which getter and setter methods to use. In this case, Hibernate will search for getDate(), setDate(), getTitle(), and setTitle() methods.

note

Why does the date property mapping include the column attribute, but the title does not? Without the column attribute, Hibernate by default uses the property name as the column name. This works for title, however, date is a reserved keyword in most databases, so you will need to map it to a different name.

The title mapping also lacks a type attribute. The types declared and used in the mapping files are not Java data types; they are not SQL database types either. These types are 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 cannot know if the property, which is of java.util.Date, should map to a SQL date, timestamp, or time column. Full date and time information is preserved by mapping the property with a timestamp converter.

tip

Hibernate makes this mapping type determination using reflection when the mapping files are processed. This can take time and resources, so if startup performance is important you should consider explicitly defining the type to use.

Hibernate configuration

At this point, you should have the persistent class and its mapping file in place. It is now time to configure Hibernate. First let's set up HSQLDB to run in "server mode."

note

We do this so that the data remains between runs.

We will utilize the Maven exec plugin to launch the HSQLDB server by running:

mvn exec:java -Dexec.mainClass="org.hsqldb.Server" -Dexec.args="-database.0 file:target/data/tutorial"

You will 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 HSQLDB, delete all files in the target/data directory, and start HSQLDB again.

Hibernate will be connecting to the database on behalf of your application, so it needs to know how to obtain connections. For this tutorial we will be using a standalone connection pool (as opposed to a javax.sql.DataSource). Hibernate comes with support for two third-party open source JDBC connection pools: c3p0 and proxool. However, we will be using the Hibernate built-in connection pool for this tutorial.

caution

The built-in Hibernate connection pool is in no way intended for production use. It lacks several features found on any decent connection pool.

For Hibernate's configuration, we can use a simple hibernate.properties file, a more sophisticated hibernate.cfg.xml file, or even complete programmatic setup. Most users prefer the XML configuration file:

Create a new XML file under the following path:

hibernate-tutorial/src/main/resources/hibernate.cfg.xml

Paste the following contents into this 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">update</property>
	<mapping resource="org/hibernate/tutorial/domain/Event.hbm.xml"/>
	</session-factory>
</hibernate-configuration>

note

Notice that this configuration file specifies a different DTD

You configure Hibernate's SessionFactory. SessionFactory is a global factory responsible for a particular database. If you have several databases, for easier startup you should use several <session-factory> configurations in several configuration files.

The first four property elements contain the necessary configuration for the JDBC connection. The dialect property element specifies the particular SQL variant Hibernate generates.

tip

In most cases, Hibernate is able to properly determine which dialect to use. See Section 25.3, “Dialect resolution” for more information.

Hibernate's automatic session management for persistence contexts is particularly useful in this context. The hbm2ddl.auto option turns on automatic generation of database schemas directly into the database. This can also be turned off by removing the configuration option, or redirected to a file with the help of the SchemaExport Ant task. Finally, add the mapping file(s) for persistent classes to the configuration.

building with Maven

We will now build the tutorial with Maven. You will need to have Maven installed; it is available from the Maven download page. Maven will read the pom.xml file we created earlier and know how to perform some basic project tasks. First, lets run the compile goal to make sure we can compile everything so far.

Your output should look something like this:

[hibernateTutorial]$ mvn compile
[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] Building First Hibernate Tutorial
[INFO]    task-segment: [compile]
[INFO] ------------------------------------------------------------------------
[INFO] [resources:resources]
[INFO] Using default encoding to copy filtered resources.
[INFO] [compiler:compile]
[INFO] Compiling 1 source file to /home/steve/projects/sandbox/hibernateTutorial/target/classes
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2 seconds
[INFO] Finished at: Tue Jun 09 12:25:25 CDT 2009
[INFO] Final Memory: 5M/547M
[INFO] ------------------------------------------------------------------------

startup and helpers

It is time now to load and store some Event objects -- but first we need to complete the setup with some infrastructure code. We'll start Hibernate by building a global SessionFactory object and storing it somewhere for easy access in application code. A SessionFactory is used to obtain Session instances. A Session represents a single-threaded unit of work. The SessionFactory is a thread-safe global object that is instantiated once.

Let's create a HibernateUtil helper class that takes care of startup and makes accessing the SessionFactory more convenient. Create a new Java file at

src/main/java/org/hibernate/tutorial/util/HibernateUtil.java

Copy and paste the following code:

package org.hibernate.tutorial.util;

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

public class HibernateUtil
{
	
	private static final SessionFactory gpSessionFactory = buildSessionFactory();
	
	private static SessionFactory buildSessionFactory()
	{
		try
		{
			// Create the SessionFactory from hibernate.cfg.xml
			return 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 gpSessionFactory;
	}
	
}

This class not only produces the global SessionFactory reference in its static initializer; it also hides the fact that it uses a static singleton. We might just as well have looked up the SessionFactory reference from JNDI in an application server or any other location for that matter.

If you give the SessionFactory a name in your configuration, Hibernate will try to bind it to JNDI under that name after it has been built. Another, better option is to use a JMX deployment and let the JMX-capable container instantiate and bind a HibernateService to JNDI. Such advanced options are discussed later.

You now need to configure a logging system. Hibernate uses commons logging and provides two choices: Log4j and JDK 1.4 logging. Most developers prefer Log4j: copy log4j.properties from the Hibernate distribution in the etc folder to your src folder, next to hibernate.cfg.xml. If you prefer to have more verbose output than that provided in the example configuration, you can change the settings. By default, only the Hibernate startup message is shown on stdout.

The tutorial infrastructure is complete and you are now ready to do some real work with Hibernate.

loading and storing objects

We are now ready to start doing some real work with Hibernate. Let's start by writing an EventManager class with a main() method. Create a new Java file at:

src/main/java/org/hibernate/tutorial/EventManager.java

Copy and paste the following code:

package org.hibernate.tutorial;

import java.util.List;

import java.util.Date;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.tutorial.domain.Event;
import org.hibernate.tutorial.util.HibernateUtil;

public class EventManager
{

	private static final String STORE_COMMAND = "store";

	private static final String LIST_COMMAND = "list";
	
	private static final String DEFAULT_EVENT_TITLE = "My Event";
	
	public static void main(String[] iasArgs)
	{
		String vsCommand = iasArgs[0];
		EventManager vpEventManasger = new EventManager();
		if (vsCommand.equals(STORE_COMMAND))
		{
			Date vpNow = new Date();
			vpEventManasger.createAndStoreEvent(DEFAULT_EVENT_TITLE, vpNow);
		}
		SessionFactory vpSessionFactory = HibernateUtil.getSessionFactory();
		vpSessionFactory.close();
	}
	
	private void createAndStoreEvent(String isTitle, Date ipDate)
	{
		SessionFactory vpSessionFactory = HibernateUtil.getSessionFactory();
		Session session = vpSessionFactory.getCurrentSession();
		Transaction vpTransaction = session.beginTransaction();
		Event vpEvent = new Event();
		vpEvent.setTitle(isTitle);
		vpEvent.setDate(ipDate);
		session.save(vpEvent);
		vpTransaction.commit();
	}	

}

In createAndStoreEvent(), we created a new Event object and handed it over to Hibernate. At that point, Hibernate takes care of the SQL and executes an INSERT into the database.

A Session is designed to represent a single unit of work (a single atmoic piece of work to be performed). For now we will 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 we use the Hibernate Transaction API. In this particular case we are using JDBC-based transactional semantics, but it could also run with JTA.

What does sessionFactory.getCurrentSession() do? First, you can call it as many times and anywhere you like once you get hold of your SessionFactory. The getCurrentSession() method always returns the "current" unit of work. Remember that we configured the configuration option for this mechanism to be "thread" in src/main/resources/hibernate.cfg.xml? Due to that setting, the context of a current unit of work is bound to the current Java thread that executes the application.

important

Hibernate offers three methods of current session tracking. The "thread" based method is not intended for production use; it is merely useful for prototyping and tutorials such as this one. Current session tracking is discussed in more detail later on.

A Session begins when the first call to getCurrentSession() is made for the current thread. 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.

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. However 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. Even though it is used in the following examples, consider the session-per-operation an anti-pattern. A real web application is shown later in the tutorial which will help illustrate this.

See Chapter 11, Transactions and Concurrency for more information about transaction handling and demarcation. The previous example also skipped any error handling and rollback.

Let's compile and run what we have so far. We'll do this with Maven, because it automatically sets up the necessary classpath environment. Execute the following commands, from the root of your project:

mvn compile
mvn exec:java -Dexec.mainClass="org.hibernate.tutorial.EventManager" -Dexec.args="store"

After you execute the second command, you should see Hibernate starting up and, depending on your configuration, lots of log output. Towards the end, the following line will be displayed:

[java] Hibernate: insert into EVENTS (EVENT_DATE, title, EVENT_ID) values (?, ?, ?)

This is the INSERT executed by Hibernate. Run the program as many times as you please.

To list the stored events, we'll add, to the main() method, support a "list" command:

else if (vsCommand.equals(LIST_COMMAND))
{
	List<Event> vpEvents = vpEventManasger.listEvents();
	for (Event vpEvent: vpEvents)
	{
		String vsTitle = vpEvent.getTitle();
		Date vpDate = vpEvent.getDate();
		System.out.println("Event: " + vsTitle + " Time: " + vpDate);
	}
	}

Don't forget to add the new string constant:

private static final String LIST_COMMAND = "list";

And finally, the implementation of listEvents():

private List<Event> listEvents()
{
	SessionFactory vpSessionFactory = HibernateUtil.getSessionFactory();
	Session session = vpSessionFactory.getCurrentSession();
	Transaction vpTransaction = session.beginTransaction();
	List<Event> result = session.createQuery("from Event").list();
	vpTransaction.commit();
	return result;
}

In listEvents(), we are using a Hibernate Query Language (HQL) 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. See Chapter 14, HQL: The Hibernate Query Language for more information.

Now we can invoke our new functionality, again using the Maven exec plugin. The command is:

mvn exec:java -Dexec.mainClass="org.hibernate.tutorial.EventManager" -Dexec.args="list"

Personal tools
Namespaces

Variants
Actions
Navigation
Toolbox