Get started with Hibernate

Introducing and configuring Hibernate

It's good to understand the need for object/relational mapping (ORM) in Java applications, but you're probably eager to see Hibernate in action. We'll start by showing you a simple example that demonstrates some of its power.

As you're probably aware, it's traditional for a programming book to start with a "Hello World" example. In this chapter, we follow that tradition by introducing Hibernate with a relatively simple "Hello World" program. However, simply printing a message to a console window won't be enough to really demonstrate Hibernate. Instead, our program will store newly created objects in the database, update them, and perform queries to retrieve them from the database.

In addition to the canonical "Hello World" example, we introduce the core Hibernate APIs and give details for a basic configuration.

"Hello World" with Hibernate

Hibernate applications define persistent classes that are "mapped" to database tables. Our "Hello World" example consists of one class and one mapping file. Let's see what a simple persistent class looks like, how the mapping is specified, and some of the things we can do with instances of the persistent class using Hibernate.

The objective of our sample application is to store messages in a database and to retrieve them for display. The application has a simple persistent class, Message, which represents these printable messages. Our Message class is shown in Listing 1.

Listing 1. Message.java: A simple persistent class

package hello;
public class Message {
   private Long id;
   private String text;
   private Message nextMessage;
   private Message() {}
   public Message(String text) {
      this.text = text;
   }
   public Long getId() {
      return id;
   }
   private void setId(Long id) {
      this.id = id;
   }
   public String getText() {
      return text;
   }
   public void setText(String text) {
      this.text = text;
   }
   public Message getNextMessage() {
      return nextMessage;
   }
   public void setNextMessage(Message nextMessage) {
      this.nextMessage = nextMessage;
   }
}

Our Message class has three attributes: the identifier attribute, the text of the message, and a reference to another Message. The identifier attribute allows the application to access the database identity—the primary key value—of a persistent object. If two instances of Message have the same identifier value, they represent the same row in the database. We've chosen Long for the type of our identifier attribute, but this isn't a requirement. Hibernate allows virtually anything for the identifier type, as you'll see later.

You may have noticed that all attributes of the Message class have JavaBean-style property accessor methods. The class also has a constructor with no parameters. The persistent classes we use in our examples will almost always look something like this.

Instances of the Message class may be managed (made persistent) by Hibernate, but they don't have to be. Since the Message object doesn't implement any Hibernate-specific classes or interfaces, we can use it like any other Java class:

Message message = new Message("Hello World");
System.out.println( message.getText() );

This code fragment does exactly what we've come to expect from "Hello World" applications: It prints "Hello World" to the console. It might look like we're trying to be cute here; in fact, we're demonstrating an important feature that distinguishes Hibernate from some other persistence solutions, such as EJB (Enterprise JavaBean) entity beans. Our persistent class can be used in any execution context—no special container is needed. Of course, you came here to see Hibernate itself, so let's save a new Message to the database:

Session session = getSessionFactory().openSession();
Transaction tx = session.beginTransaction();
Message message = new Message("Hello World");
session.save(message);
tx.commit();
session.close();

This code calls the Hibernate Session and Transaction interfaces. (We'll get to that getSessionFactory() call soon.) It results in the execution of something similar to the following SQL:

insert into MESSAGES (MESSAGE_ID, MESSAGE_TEXT, NEXT_MESSAGE_ID)
values (1, 'Hello World', null)

Hold on—the MESSAGE_ID column is being initialized to a strange value. We didn't set the id property of message anywhere, so we would expect it to be null, right? Actually, the id property is special: It's an identifier property—it holds a generated unique value. (We'll discuss how the value is generated later.) The value is assigned to the Message instance by Hibernate when save() is called.

For this example, we assume that the MESSAGES table already exists. Of course, we want our "Hello World" program to print the message to the console. Now that we have a message in the database, we're ready to demonstrate this. The next example retrieves all messages from the database, in alphabetical order, and prints them:

Session newSession = getSessionFactory().openSession();
Transaction newTransaction = newSession.beginTransaction();
List messages =
      newSession.find("from Message as m order by m.text asc");
System.out.println( messages.size() + " message(s) found:" );
for ( Iterator iter = messages.iterator(); iter.hasNext(); ) {
   Message message = (Message) iter.next();
   System.out.println( message.getText() );
}
newTransaction.commit();
newSession.close();

The literal string "from Message as m order by m.text asc" is a Hibernate query, expressed in Hibernate's own object-oriented Hibernate Query Language (HQL). This query is internally translated into the following SQL when find() is called:

select m.MESSAGE_ID, m.MESSAGE_TEXT, m.NEXT_MESSAGE_ID
from MESSAGES m
order by m.MESSAGE_TEXT asc

The code fragment prints:

1 message(s) found:
Hello World

If you've never used an ORM tool like Hibernate before, you were probably expecting to see the SQL statements somewhere in the code or metadata. They aren't there. All SQL is generated at runtime (actually at startup, for all reusable SQL statements).

To allow this magic to occur, Hibernate needs more information about how the Message class should be made persistent. This information is usually provided in an XML mapping document. The mapping document defines, among other things, how properties of the Message class map to columns of the MESSAGES table. Let's look at the mapping document in Listing 2.

Listing 2. A simple Hibernate XML mapping

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
   "-//Hibernate/Hibernate Mapping DTD//EN"
   "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">
<hibernate-mapping>
   <class
      name="hello.Message"
      table="MESSAGES">
      <id
         name="id"
         column="MESSAGE_ID">
         <generator class="increment"/>
      </id>
      <property
         name="text"
         column="MESSAGE_TEXT"/>
      <many-to-one
         name="nextMessage"
         cascade="all"
         column="NEXT_MESSAGE_ID"/>
   </class>
</hibernate-mapping>

The mapping document tells Hibernate that the Message class is to be persisted to the MESSAGES table, that the identifier property maps to a column named MESSAGE_ID, that the text property maps to a column named MESSAGE_TEXT, and that the property named nextMessage is an association with many-to-one multiplicity that maps to a column named NEXT_MESSAGE_ID. (Don't worry about the other details for now.)

As you can see, the XML document isn't difficult to understand. You can easily write and maintain it by hand. Whichever method you choose, Hibernate has enough information to completely generate all the SQL statements that would be needed to insert, update, delete, and retrieve instances of the Message class. You no longer need to write these SQL statements by hand.

Note
Many Java developers have complained of the "metadata hell" that accompanies J2EE development. Some have suggested a movement away from XML metadata back to plain Java code. Although we applaud this suggestion for some problems, ORM represents a case where text-based metadata really is necessary. Hibernate has sensible defaults that minimize typing and a mature document type definition that can be used for auto-completion or validation in editors. You can even automatically generate metadata with various tools.

Now, let's change our first message and, while we're at it, create a new message associated with the first, as shown in Listing 3.

Listing 3. Updating a message

Session session = getSessionFactory().openSession();
Transaction tx = session.beginTransaction();
// 1 is the generated id of the first message
   Message message =
(Message) session.load( Message.class, new Long(1) );
message.setText("Greetings Earthling");
Message nextMessage = new Message("Take me to your leader (please)");
message.setNextMessage( nextMessage );
tx.commit();
session.close();

This code calls three SQL statements inside the same transaction:

select m.MESSAGE_ID, m.MESSAGE_TEXT, m.NEXT_MESSAGE_ID
from MESSAGES m
where m.MESSAGE_ID = 1
insert into MESSAGES (MESSAGE_ID, MESSAGE_TEXT, NEXT_MESSAGE_ID)
values (2, 'Take me to your leader (please)', null)
update MESSAGES
set MESSAGE_TEXT = 'Greetings Earthling', NEXT_MESSAGE_ID = 2
where MESSAGE_ID = 1

Notice how Hibernate detected the modification to the text and nextMessage properties of the first message and automatically updated the database. We've taken advantage of a Hibernate feature called automatic dirty checking: this feature saves us the effort of explicitly asking Hibernate to update the database when we modify the state of an object inside a transaction. Similarly, you can see that the new message was made persistent when a reference was created from the first message. This feature is called cascading save: it saves us the effort of explicitly making the new object persistent by calling save(), as long as it's reachable by an already persistent instance. Also notice that the ordering of the SQL statements isn't the same as the order in which we set property values. Hibernate uses a sophisticated algorithm to determine an efficient ordering that avoids database foreign key constraint violations but is still sufficiently predictable to the user. This feature is called transactional write-behind.

If we run "Hello World" again, it prints:

2 message(s) found:
Greetings Earthling
Take me to your leader (please)

This is as far as we'll take the "Hello World" application. Now that we finally have some code under our belt, we'll take a step back and present an overview of Hibernate's main APIs.

Understanding the architecture

The programming interfaces are the first thing you have to learn about Hibernate in order to use it in the persistence layer of your application. A major objective of API design is to keep the interfaces between software components as narrow as possible. In practice, however, ORM APIs aren't especially small. Don't worry, though; you don't have to understand all the Hibernate interfaces at once. The figure below illustrates the roles of the most important Hibernate interfaces in the business and persistence layers.

High-level overview of the Hibernate API in a layered architecture

We show the business layer above the persistence layer, since the business layer acts as a client of the persistence layer in a traditionally layered application. Note that some simple applications might not cleanly separate business logic from persistence logic; that's okay—it merely simplifies the diagram.

The Hibernate interfaces shown in the figure above may be approximately classified as follows:

  • Interfaces called by applications to perform basic CRUD (create/read/update/delete) and querying operations. These interfaces are the main point of dependency of application business/control logic on Hibernate. They include Session, Transaction, and Query.
  • Interfaces called by application infrastructure code to configure Hibernate, most importantly, the Configuration class.
  • Callback interfaces that allow the application to react to events occurring inside Hibernate, such as Interceptor, Lifecycle, and Validatable.
  • Interfaces that allow extension of Hibernate's powerful mapping functionality, such as UserType, CompositeUserType, and IdentifierGenerator. These interfaces are implemented by application infrastructure code (if necessary).

Hibernate makes use of existing Java APIs, including JDBC (Java Database Connectivity), Java Transaction API (JTA), and Java Naming and Directory Interface (JNDI). JDBC provides a rudimentary level of abstraction of functionality common to relational databases, allowing almost any database with a JDBC driver to be supported by Hibernate. JNDI and JTA allow Hibernate to be integrated with J2EE application servers.

In this section, we don't cover the detailed semantics of Hibernate API methods, just the role of each of the primary interfaces. You can find most of these interfaces in the package net.sf.hibernate. Let's take a brief look at each interface in turn.

The core interfaces

The five core interfaces are used in just about every Hibernate application. Using these interfaces, you can store and retrieve persistent objects and control transactions.

Session interface

The Session interface is the primary interface used by Hibernate applications. An instance of Session is lightweight and is inexpensive to create and destroy. This is important because your application will need to create and destroy sessions all the time, perhaps on every request. Hibernate sessions are not thread-safe and should by design be used by only one thread at a time.

The Hibernate notion of a session is something between connection and transaction. It may be easier to think of a session as a cache or collection of loaded objects relating to a single unit of work. Hibernate can detect changes to the objects in this unit of work. We sometimes call the Session a persistence manager because it's also the interface for persistence-related operations such as storing and retrieving objects. Note that a Hibernate session has nothing to do with the Web-tier HttpSession. When we use the word session, we mean the Hibernate session. We sometimes use user session to refer to the HttpSession object.

SessionFactory interface

The application obtains Session instances from a SessionFactory. Compared to the Session interface, this object is much less exciting.

The SessionFactory is certainly not lightweight! It's intended to be shared among many application threads. There is typically a single SessionFactory for the whole application—created during application initialization, for example. However, if your application accesses multiple databases using Hibernate, you'll need a SessionFactory for each database.

The SessionFactory caches generate SQL statements and other mapping metadata that Hibernate uses at runtime. It also holds cached data that has been read in one unit of work and may be reused in a future unit of work (only if class and collection mappings specify that this second-level cache is desirable).

Configuration interface

The Configuration object is used to configure and bootstrap Hibernate. The application uses a Configuration instance to specify the location of mapping documents and Hibernate-specific properties and then create the SessionFactory.

Even though the Configuration interface plays a relatively small part in the total scope of a Hibernate application, it's the first object you'll meet when you begin using Hibernate.

Transaction interface

The Transaction interface is an optional API. Hibernate applications may choose not to use this interface, instead managing transactions in their own infrastructure code. A Transaction abstracts application code from the underlying transaction implementation—which might be a JDBC transaction, a JTA UserTransaction, or even a Common Object Request Broker Architecture (CORBA) transaction—allowing the application to control transaction boundaries via a consistent API. This helps to keep Hibernate applications portable between different kinds of execution environments and containers.

Query and Criteria interfaces

The Query interface allows you to perform queries against the database and control how the query is executed. Queries are written in HQL or in the native SQL dialect of your database. A Query instance is used to bind query parameters, limit the number of results returned by the query, and finally to execute the query.

The Criteria interface is very similar; it allows you to create and execute object-oriented criteria queries.

To help make application code less verbose, Hibernate provides some shortcut methods on the Session interface that let you invoke a query in one line of code. We won't use these shortcuts; instead, we'll always use the Query interface.

A Query instance is lightweight and can't be used outside the Session that created it.

Callback interfaces

Callback interfaces allow the application to receive a notification when something interesting happens to an object—for example, when an object is loaded, saved, or deleted. Hibernate applications don't need to implement these callbacks, but they're useful for implementing certain kinds of generic functionality, such as creating audit records.

The Lifecycle and Validatable interfaces allow a persistent object to react to events relating to its own persistence lifecycle. The persistence lifecycle is encompassed by an object's CRUD operations. The Hibernate team was heavily influenced by other ORM solutions that have similar callback interfaces. Later, they realized that having the persistent classes implement Hibernate-specific interfaces probably isn't a good idea, because doing so pollutes our persistent classes with nonportable code. Since these approaches are no longer favored, we don't discuss them.

The Interceptor interface was introduced to allow the application to process callbacks without forcing the persistent classes to implement Hibernate-specific APIs. Implementations of the Interceptor interface are passed to the persistent instances as parameters.

Types

A fundamental and very powerful element of the architecture is Hibernate's notion of a Type. A Hibernate Type object maps a Java type to a database column type (actually, the type may span multiple columns). All persistent properties of persistent classes, including associations, have a corresponding Hibernate type. This design makes Hibernate extremely flexible and extensible.

There is a rich range of built-in types, covering all Java primitives and many JDK classes, including types for java.util.Currency, java.util.Calendar, byte[], and java.io.Serializable.

Even better, Hibernate supports user-defined custom types. The interfaces UserType and CompositeUserType are provided to allow you to add your own types. You can use this feature to allow commonly used application classes such as Address, Name, or MonetaryAmount to be handled conveniently and elegantly. Custom types are considered a central feature of Hibernate, and you're encouraged to put them to new and creative uses!

Extension interfaces

Much of the functionality that Hibernate provides is configurable, allowing you to choose between certain built-in strategies. When the built-in strategies are insufficient, Hibernate will usually let you plug in your own custom implementation by implementing an interface. Extension points include:

  • Primary key generation (IdentifierGenerator interface)
  • SQL dialect support (Dialect abstract class)
  • Caching strategies (Cache and CacheProvider interfaces)
  • JDBC connection management (ConnectionProvider interface)
  • Transaction management (TransactionFactory, Transaction, and TransactionManagerLookup interfaces)
  • ORM strategies (ClassPersister interface hierarchy)
  • Property access strategies (PropertyAccessor interface)
  • Proxy creation (ProxyFactory interface)

Hibernate ships with at least one implementation of each of the listed interfaces, so you don't usually need to start from scratch if you wish to extend the built-in functionality.

By now, you can see that before we can start writing any code that uses Hibernate, we must answer this question: How do we get a Session to work with?

Basic configuration

We've looked at an example application and examined Hibernate's core interfaces. To use Hibernate in an application, you need to know how to configure it. Hibernate can be configured to run in almost any Java application and development environment. Generally, Hibernate is used in two- and three-tiered client-server applications, with Hibernate deployed only on the server. The client application is usually a Web browser, but Swing and SWT (Simple Widget Toolkit) client applications aren't uncommon. Although we concentrate on multitiered Web applications, our explanations apply equally to other architectures, such as command line applications. It's important to understand the difference in configuring Hibernate for managed and nonmanaged environments:

  • Managed environment—Pools resources such as database connections and allows transaction boundaries and security to be specified declaratively (that is, in metadata). A J2EE application server such as JBoss, BEA WebLogic, or IBM WebSphere implements the standard (J2EE-specific) managed environment for Java.
  • Nonmanaged environment—Provides basic concurrency management via thread pooling. A servlet container like Jetty or Tomcat provides a nonmanaged server environment for Java Web applications. A standalone desktop or command line application is also considered nonmanaged. Nonmanaged environments don't provide automatic transaction or resource management or security infrastructure. The application itself manages database connections and demarcates transaction boundaries.

Hibernate attempts to abstract the environment in which it's deployed. In the case of a nonmanaged environment, Hibernate handles transactions and JDBC connections (or delegates to application code that handles these concerns).

In managed environments, Hibernate integrates with container-managed transactions and datasources. Hibernate can be configured for deployment in both environments. In both managed and nonmanaged environments, the first thing you must do is start Hibernate. In practice, doing so is very easy: You have to create a SessionFactory from a Configuration.

Creating a SessionFactory

In order to create a SessionFactory, you first create a single instance of Configuration during application initialization and use it to set the location of the mapping files. Once configured, the Configuration instance is used to create the SessionFactory. After the SessionFactory is created, you can discard the Configuration class.

The following code starts Hibernate:

Configuration cfg = new Configuration();
cfg.addResource("hello/Message.hbm.xml");
cfg.setProperties( System.getProperties() );
SessionFactory sessions = cfg.buildSessionFactory();

The location of the mapping file, Message.hbm.xml, is relative to the root of the application classpath. For example, if the classpath is the current directory, the Message.hbm.xml file must be in the hello directory. XML mapping files must be placed in the classpath. In this example, we also use the system properties of the virtual machine to set all other configuration options (which might have been set before by application code or as startup options).

Method chaining

Method chaining is a programming style supported by many Hibernate interfaces. This style is more popular in Smalltalk than in Java and is considered by some people to be less readable and more difficult to debug than the more accepted Java style. However, it's very convenient in most cases.

Most Java developers declare setter or adder methods to be of type void, meaning they return no value. In Smalltalk, which has no void type, setter or adder methods usually return the receiving object. This would allow us to rewrite the previous code example as follows:

SessionFactory sessions = new Configuration()
   .addResource("hello/Message.hbm.xml")
   .setProperties( System.getProperties() )
   .buildSessionFactory();

Notice that we didn't need to declare a local variable for the Configuration. We use this style in some code examples; but if you don't like it, you don't need to use it yourself. If you do use this coding style, it's better to write each method invocation on a different line. Otherwise, it might be difficult to step through the code in your debugger.

By convention, Hibernate XML mapping files are named with the .hbm.xml extension. Another convention is to have one mapping file per class, rather than have all your mappings listed in one file (which is possible but considered bad style). Our "Hello World" example had only one persistent class, but let's assume we have multiple persistent classes, with an XML mapping file for each. Where should we put these mapping files?

The Hibernate documentation recommends that the mapping file for each persistent class be placed in the same directory as that class. For instance, the mapping file for the Message class would be placed in the hello directory in a file named Message.hbm.xml. If we had another persistent class, it would be defined in its own mapping file. We suggest that you follow this practice. The monolithic metadata files encouraged by some frameworks, such as the struts-config.xml found in Struts, are a major contributor to "metadata hell." You load multiple mapping files by calling addResource() as often as you have to. Alternatively, if you follow the convention just described, you can use the method addClass(), passing a persistent class as the parameter:

SessionFactory sessions = new Configuration()
   .addClass(org.hibernate.auction.model.Item.class)
   .addClass(org.hibernate.auction.model.Category.class)
   .addClass(org.hibernate.auction.model.Bid.class)
   .setProperties( System.getProperties() )
   .buildSessionFactory();

The addClass() method assumes that the name of the mapping file ends with the .hbm.xml extension and is deployed along with the mapped class file.

We've demonstrated the creation of a single SessionFactory, which is all that most applications need. If another SessionFactory is needed—if there are multiple databases, for example—you repeat the process. Each SessionFactory is then available for one database and ready to produce Sessions to work with that particular database and a set of class mappings.

Of course, there is more to configuring Hibernate than just pointing to mapping documents. You also need to specify how database connections are to be obtained, along with various other settings that affect the behavior of Hibernate at runtime. The multitude of configuration properties may appear overwhelming (a complete list appears in the Hibernate documentation), but don't worry; most define reasonable default values, and only a handful are commonly required.

To specify configuration options, you may use any of the following techniques:

  • Pass an instance of java.util.Properties to Configuration.setProperties()
  • Set system properties using java -Dproperty=value
  • Place a file called hibernate.properties in the classpath
  • Include <property> elements in hibernate.cfg.xml in the classpath

The first and second options are rarely used except for quick testing and prototypes, but most applications need a fixed configuration file. Both the hibernate.properties and the hibernate.cfg.xml files provide the same function: to configure Hibernate. Which file you choose to use depends on your syntax preference. It's even possible to mix both options and have different settings for development and deployment

A rarely used alternative option is to allow the application to provide a JDBC Connection when it opens a Hibernate Session from the SessionFactory (for example, by calling sessions.openSession(myConnection)). Using this option means that you don't have to specify any database connection properties. We don't recommend this approach for new applications that can be configured to use the environment's database connection infrastructure (for example, a JDBC connection pool or an application server datasource).

Christian Bauer is a member of the Hibernate developer team and is also responsible for the Hibernate Website and documentation. Bauer is interested in relational database systems and sound data management in Java applications. He works as a developer and consultant for JBoss and lives in Frankfurt, Germany. Gavin King is the founder of the Hibernate project and lead developer. He is an enthusiastic proponent of agile development and open source software. King is helping integrate ORM technology into the J2EE standard as a member of the EJB 3 Expert Group. He is a developer and consultant for JBoss, based in Melbourne, Australia.

Learn more about this topic

Join the discussion
Be the first to comment on this article. Our Commenting Policies
See more