DekGenius.com
Previous Section  < Day Day Up >  Next Section

7.2 What Is Hibernate?

Hibernate is an open source project that lets you store plain Java objects to a database. Unlike JDO, Hibernate works only with relational databases, and only over JDBC. Hibernate's persistence strategy is known as transparent persistence because the model that you build contains no persistence code of any kind. By contrast, some other persistence strategies make you change your code (EJB), or deal with rows and columns instead of POJOs (JDBC). You don't always need a full persistence framework to do database programming but for some problems, it makes things easier in many ways:

  • You'll be able to work with Java objects, instead of relational tables.

  • Your whole application won't need to change if either the objects or database schema change.

  • You won't have to worry about persistence details. Saving a whole object saves all of its fields and all of its attributes, even if they are objects or collections of objects.

The result is a cleaner, simpler application with a better separation of concerns. The details about the object model do not have to be muddied with the structure of your database.

7.2.1 Simple Example

The best way to learn Hibernate is by example. Here, I use a discussion board. Figure 7-1 shows the classes in our model and the relationship between each of them. The persistent classes are topics, which contain posts. Each post is associated with a user.

Figure 7-1. The object model for the message board application contains three classes
figs/bflJ_0701.gif


If you were to implement this application with JDBC, you'd have to maintain the relationship between topics and posts manually. Using Hibernate, you do most of the heavy lifting with simple configuration. You deal primarily in the Java space and let Hibernate manage the complexities of persistence. Follow these steps to build a Hibernate application:

  1. Write your object model. Transparent persistence frameworks let you write an object model that has no specific database-aware code at all. By hiding the details from your application, you can better focus on what you want the object model to do.

  2. Build your database schema. Your persistence model only affects your database schema in very small ways, if at all.

  3. Configure your mapping file. The mapping file connects the pieces of the database schema, like tables and fields, with pieces of your application, like classes and attributes.

  4. Configure Hibernate. You need to tell Hibernate some details about your application, such as where to find your JDBC driver and which relational database you're using.

  5. Use the model. If your persistent object model is like the puppet, you need a puppeteer: an application that knows when to pull the right strings, saving and retrieving data from the model when your application needs it.

I'll show you how to write all of these by hand. In truth, you may use tools to generate your mapping, and possibly your schema.

7.2.2 Writing the Object Model

Let's start with the object model. Your object model deals only with your problem domain, while the persistence framework hides the details. I create classes for each part of the model shown in Figure 7-1 (User, Topic, and Post). First, here's the User:

package discussion;

public class User {

  String id = null;
  String password = null;
  
  User (String userID, String pw) {
    id = userID;
    password = pw;
  }
[1]    User ( ) {}
[2]    public String getID ( ) {
    return id;
  }
  public void setID(String newUser) {
    id = newUser;
  }

  public String getPassword ( ) {
    return password;
  }
  public void setPassword (String pw) {
    password = pw;
  }
}

Notice that your object model has a couple of limitations:

[1] You need to allow a default constructor without parameters because Hibernate will create users. I also like to allow convenience constructors that set a user up in an acceptable state, but Hibernate will not use these to create its objects.
[2] Hibernate uses reflection to access properties. Even though the Java reflection API allows for field access without getters and setters, early versions of Hibernate required them. Hibernate has recently added support to directly access fields; most of the generators and existing code still use them.

Let's move on to a more complex class. Each topic has a series of posts it must maintain. You need to be able to add and delete posts.

package discussion;

import java.util.*;

public class Topic {

  Topic (String topicID) {
    id = topicID;
  }

  Topic ( ) {
  }

  String id = "Unnamed Topic";
[1]    List posts = new ArrayList( );
  Date timestamp = new Date( );
  Date modified = new Date( );
[2]    public String getID( ) {
    return id;
  }
  public void setID(String topic) {
    id = topic;
  }

  public List getPosts( ) {
    return posts;
  }
  public void setPosts(List p) {
    posts = p;
  }

  public Date getTimestamp( ) {
    return timestamp;
  }
  public void setTimestamp(Date t) {
    timestamp = t;
  }

  public Date getModified( ) {
    return timestamp;
  }
  public void setModified(Date t) {
    timestamp = t;
  }
}

Here's what the annotations mean:

[1] To manage our posts, I'm using a Java ArrayList. Rather than adding an API to Topic to add, delete, and update posts, I just provide public access to the Posts property and let my users access the list directly. Hibernate integrates with Java collections well, with explicit support for Sets, Bags, and Maps.
[2] Once again, you see the getters and setters for each property. In addition to the name of the topic and the list of posts, I've added timestamps: one to show when a user modifies a topic and one that indicates a topic's creation date.

The code for Post is the same. Once again, the class has properties, constructors, and accessors.

package discussion;

import java.util.*;

public class Post {
  Long id = null;
  String subject = "No subject";
  String body = "Empty post";
  User user = null;
[1]    Post (User u, String s, String b) {
    user = u;
    subject = s;
    body = b;
  }

  Post( ) {}
[2]    public Long getID ( ) {
    return id;
  }
  public void setID(Long newPost) {
    id = newPost;
  }

  public String getSubject( ) {
    return subject;
  }
  public void setSubject(String s) {
    subject = s;
  }

  public String getBody( ) {
    return body;
  }
  public void setBody(String b) {
    body = b;
  }

  public User getUser( ) {
    return user;
  }
  public void setUser(String u) {
    user = u;
  }

  public String getBody( ) {
    return body;
  }
  public void setBody(String b) {
    body = b;
  }
}

Notice two special properties:

[1] User: as with most object models, this one has references to other objects. Later, I'll have a foreign key in the Posts table that maps to the User table.
[2] The ID is a unique identifier for a given post. I can choose to let Hibernate create the ID for me when it saves the object or I can choose to assign my own ID, as I did with User and Topic.

That's it. The clarity is marvelous. The model does one thing: represent the real-world rules for a message board. It's not cluttered with persistence details like transactions, updates, or queries. You can easily tell what the object model does. You can test your object model without testing the persistence. You can also stick to Java, your native programming language, for expressing a model. Earlier, I mentioned that Java developers built many persistence frameworks before they began to get them right. Through all of the failures, the tantalizing level of simplicity that transparent persistence promised motivated them to keep working to get it right.

7.2.3 Building the Schema

The next step for building a Hibernate application is to build your persistent schema. I choose to create a script that accomplishes three tasks for each table: it drops the table (and indices, if applicable), creates the table (and possibly indices), and adds any special data.

Here's the script for the discussion application:

[1]   drop table users;
[2]   CREATE TABLE users (id       VARCHAR(20) NOT NULL,
                    password VARCHAR(20),
                    PRIMARY KEY(id)
);

drop table topics;
CREATE TABLE topics   (id      VARCHAR(40) NOT NULL,
                      ts       TIMESTAMP,
                      modified TIMESTAMP,
                      PRIMARY KEY(id)
                      
);

drop table posts;
CREATE TABLE posts       (id          BIGINT NOT NULL,
                          subject     VARCHAR(80),
                          body        TEXT,
                          ts          TIMESTAMP, 
                          poster      VARCHAR(20),
                          topicID     VARCHAR(40),
                          PRIMARY KEY (id)
);

drop table hilo;
CREATE TABLE hilo (next BIGINT);
[3]   insert into hilo values (1);

[1] Drop the table. If the table already exists, you want to drop it before creating it. This step seems trivial, but it's useful for situations when you're rapidly changing the schema in development. If your schema already exists and is fairly rigid, skip this step. In complex schemas where tables are related via foreign keys, it is optimal to have a script to drop them for you, as you have to drop them in reverse order.
[2] Create the table. The next step is to create the table. You'll want to specify primary keys for the Hibernate ID.
[3] Insert any data that the table needs to function. If you're creating a table to help assign identifiers, you need to seed it. Similarly, if you're building a read-only table of, say, Zip codes or states, you must seed those, too.

There's a table for each of the persistent classes. I've also added the hilo table, which supports one of Hibernate's unique ID-generating algorithms. You don't have to build your schemas this way. You can actually support multiple classes with each table. (As of the publish date of this book, you cannot assign multiple tables to the same class.) You can also generate your schema with the tool schemaexport, via Ant or the command line.

7.2.4 Configuring the Mapping

In Chapter 4, I emphasized using configuration rather than coding where possible. Like most persistence frameworks, Hibernate has the same philosophy. You'll build two types of configuration. In this section, you'll configure the mapping between your object model and your database schema.

With Hibernate, all mappings start with a class. Associate that class with a database table and then associate columns of the table with properties of your class. As you've learned, each property must have a getter and a setter. First, let's look at a simple mapping that maps User.java to the relational table users:

<hibernate-mapping>
   <class name="discussion.User" table="users"> 
      <id name="ID"
          column="id"
          type="string">
          <generator class="assigned"></generator>
      </id>

      <property name="password"  column="password"  type="string" />
   </class>
</hibernate-mapping>

This simple mapping associates the class discussion.User with the table users. To fully establish the relationship between domain objects and data tables, a mapping must also connect the properties of the class with individual fields in the table. In this example, the class property ID is mapped to the id column, and the password property maps to the password column. The ID property mapping is a special kind of mapping, described below.

7.2.4.1 Identifiers

I've chosen the assigned strategy for managing this identifier because a user will want to choose his ID. You already know that each Java object has a unique identifier, even if you don't specify one. The compiler virtual machine can uniquely identify an object by the memory address. That's why two objects containing the same string may not be equal: they may be stored at different memory addresses. In order to keep track of each unique object instance, Hibernate uses a special property called identifier. You can specify a strategy for the unique identifier. This identifier property uniquely identifies an object in the database table. Hibernate has several strategies for managing identifiers. These are some interesting ones:


increment

Generates identifiers by incrementing a value.


hilo

Uses values in a column of a specified table to seed a hilo algorithm.


native

Relies - on the underlying capabilities of the database server.


UUID

Attaches - a network adapter address to an identifier, making it globally unique across a cluster. May be overkill for lighter applications.


assigned

The application assigns a unique identifier. Useful when your application already generates a unique identifier.


foreign

Lets you choose an identifier from another object. (We discuss relationships in the next section.)

Each of these approaches has strengths and weaknesses. Some, like increment, are fast and simple, but won't work in a cluster because they would generate duplicate values. Others work well in a cluster (UUID uses the network adapter MAC address to form a globally unique identifier), but may be overkill for lighter applications. And some may not function well with container-managed JTA transactions because they would inject too much contention. Don't worry. The Hibernate documentation is outstanding; it walks you safely through the minefield.

7.2.4.2 Relationships

Of course, the User mapping is straightforward because Hibernate doesn't need to do anything special. The Topic mapping will be a little more complex. A topic must manage a collection of posts, like this:

<hibernate-mapping>
   <class name="discussion.Topic" table="topics"> 
      <id name="ID"
          column="id"
          type="string">
          <generator class="assigned"></generator>
      </id>

      <bag 
           name="posts" 
           order-by="ts" 
           table="posts" 
           cascade="all"            
           inverse="false">
        <key column="topicID"/>
        <one-to-many class="discussion.Post"/> 
      </bag>
      <property name="timestamp" column="ts"       type="timestamp" />
      <property name="modified"  column="modified" type="timestamp" />
   </class>
</hibernate-mapping>

Relational databases support all kinds of relationships. Wherever you've got two tables that have columns with compatible types, you can do a join and form a relationship on the fly. You manage relationships within object-oriented programs explicitly. In our case, we've got a managed many-to-one relationship between Post and Topic. Specifically, the application maintains a list of Posts within a Topic instance.

In this mapping, the magic occurs next to the bag tag. It defines a relationship that describes the interaction between Topic and Post. I specified the name of the Java property (name="posts"), and the ordering of the collection (order-by="ts"). In the mapping, I also tell Hibernate about the underlying database structure. I specify the associated table for posts (posts) and the foreign key that refers to a given topic (key column="topicID"). I tell Hibernate to also load or delete posts when I save or load a topic (cascade="all").

By defining a series of these managed relationships, you can let Hibernate load a very complex instance, such as a car or a corporation, by saving a single, top-level instance. You can also let Hibernate delete all children when you delete a parent. Here are some of the relationships supported by Hibernate:


One-to-one

Useful when two objects share an identifier; for example, a manager and a department may have a one-to-one relationship.


One-to-many

When an object has a collection property, Hibernate maps it as a many-to-one relationship. Hibernate has native support for several simple collections, including sets, lists, and bags.


Many-to-many

When many-to-one relationships occur in two directions, you've got a many-to-many relationship. For example, one person may work on many projects, and each project can have many people. Many-to-many relationships, on the relational database side, use an intermediate mapping table. In the object model in Figure 7-2, you see only Person and Project. Person has a collection of projects, and vice versa.

Figure 7-2. Many-to-many relationships
figs/bflJ_0702.gif



Inheritance

Inheritance relationships model one object that has an is-a relationship with another. Hibernate supports three basic mapping strategies. In the first, all subclasses go into the same table. In the second, each concrete class gets its own table. In the third, each subclass gets its own table. For example, an employee is-a person. Your database might have an employee table and a person table related by a foreign key, or all employees might live directly in the person table, mixed in with customers, vendors, and other types of people.


Maps

A collection of name-value pairs is known as a map. Hibernate supports several versions of maps, including hash maps.


Components

A component relationship collects several dependent properties with a first-class Hibernate mapping. For example, a user has an address. You can group the address elements together as a component.


Composites

Hibernate supports composite keys and indexes. For example, you might collect a timestamp and machine ID together to uniquely identify a log entry. You can then use this composite as a key or as an index into a map.

This list of relationships is not comprehensive. Extend Hibernate and form your own relationships as necessary in order to support your own relationships; you shouldn't need to do so very often. Relational database modelers tend to use a few well-known constructs over and over.

7.2.4.3 Types

We saw the User and Topic mappings. Only the Post mapping remains:

<hibernate-mapping>
   <class name="discussion.Post" table="posts"> 
      <id name="ID" column="id" type="long" unsaved-value="null">
        <generator class="hilo">
          <param name="table">hilo</param>    
          <param name="column">next</param>
        </generator>
      </id>

      <many-to-one name="poster" column="poster" class="discussion.User"/>
      <property name="subject"   column="subject" type="string" />
      <property name="body"      column="body"    type="text" />
      <property name="timestamp" column="ts"      type="timestamp" />
   </class>
</hibernate-mapping>

Looking at these mappings, you may wonder why there's one type. After all, somewhere, you have to specify a type for the relational database table and a type for the Java object. Yet, in this mapping:

<property name="subject"   column="subject" type="string" />

I only specify a string, although we could actually build a persistence framework that specified two types. That architecture can be awkward to use because it's not always clear which types are compatible. Hibernate instead implements types as mappings. The Hibernate string type maps from java.lang.string to the SQL types VARCHAR and VARCHAR2. This strategy makes types easy to specify and leaves no ambiguity in the area of compatible types.

In addition, Hibernate lets you create new custom types. Further, a type can map onto more than one table of a database or onto complex objects. For example, you could map a coordinate bean with x and y properties onto two database columns with simple INTEGER types. In addition to types, you'll also see a number of other places where Hibernate provides access to classes that allow powerful and flexible extension.

    Previous Section  < Day Day Up >  Next Section