ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

Hibernate Your Data

by Davor Cengija
01/14/2004

Object-relational mapping (O/R mapping) is a common requirement of many software development projects. The activities involved in persisting data are tedious and error-prone. If we also take into consideration the inevitable change of requirements, we're in serious trouble: the data storage structure must be kept in sync with the source code. Add the portability issue, and things are becoming very, very complicated.

Hibernate will help us to painlessly store the data in permanent storage without too much hassle about choosing the kind of storage, installation, or configuration. Hibernate allows us to store any kind of objects; therefore, our application does not need to know that its data will be persisted using Hibernate. Of course, everything mentioned here can be applied in the opposite direction: fetching already prepared objects from a storage is now nearly trivial. Updating and deleting data is also available.

Before You Get Started

Before you get started you'll need the Hibernate distribution, available on the Hibernate web site, www.hibernate.org. We'll use version 2.0.3. For a database, we'll use Hypersonic SQL version 1.7.1, available at hsqldb.sourceforge.net. Hibernate also supports many open source and commercial databases, such as MySQL, PostgreSQL, Oracle, DB2, etc. It is easy to set up this tutorial for any supported database. See the official documentation for the complete list.

Related Reading

Java Data Objects
By David Jordan, Craig Russell

Note: If you don't want your classes to be persisted in a database, but say, serialized, the Hibernate API provides you with the net.sf.hibernate.persister.EntityPersister class and the net.sf.hibernate.persister.ClassPersister interface. By subclassing or implementing them, you can write your own persister classes and simply use them as needed.

After downloading all of the needed packages, we'll have to set up our testing environment. Basically, all we need to do is to put downloaded .jar files into our CLASSPATH. This includes hibernate2.jar from the Hibernate distribution and hsqldb.jar from Hypersonic's lib/ directory. Hibernate also requires quite a few additional libraries, all of them available in the <hibernate-dist>/lib directory. Not all .jars from that directory are needed, but it won't hurt if you simply use them all. Before we start investigating Hibernate, we'll first define our problem domain.

Note: Hibernate uses commons-logging from Apache. It's a smart tool and it will silently use log4j if found. Log4j is an excellent logging library and we'll use it in this tutorial. If you don't already have it (and you should!) download it from the Log4j homepage and add to your CLASSPATH. Use the sample log4j.properties provided by the Hibernate team and available in <hibernate-dist>/src.

So, You Have A Problem?

Every developer has had to perform something like the following task at least once: create an Order, put some Products in it that then become OrderItems, and then save the Order.

We'll use these simple SQL commands to set up our database:

CREATE TABLE ORDERS(
        ID VARCHAR NOT NULL PRIMARY KEY,
        ORDER_DATE TIMESTAMP NOT NULL,
        PRICE_TOTAL DOUBLE NOT NULL)

CREATE TABLE PRODUCTS(
        ID VARCHAR NOT NULL PRIMARY KEY,
        NAME VARCHAR NOT NULL,
        PRICE DOUBLE NOT NULL,
        AMOUNT INTEGER NOT NULL)

CREATE TABLE ORDER_ITEMS(
        ID VARCHAR NOT NULL PRIMARY KEY,
        ORDER_ID VARCHAR NOT NULL,
        PRODUCT_ID VARCHAR NOT NULL,
        AMOUNT INTEGER NOT NULL,
        PRICE DOUBLE NOT NULL)

This data model is rather simple. For a real, "production quality" data model we would need foreign keys, indices, additional fields, etc. For this tutorial, the above data model will suffice.

Note: If you decided to use HypersonicSQL for this tutorial, you can use the provided orders.script and orders.properties files available in the source package attached to this article.

Java Code

Although these business requirements are simple and understandable, the traditional approach of writing a bunch of prepared statements will quickly become boring. Hibernate will save us from that. All we need is a set of simple mapping files. But first, we need to write our Java classes.

Note: We'll put all of our to-be-persisted classes in the test.hibernate package, and all runner classes in the test package.

Product

This simple class defines only the necessary fields: ID, product name, price of the product, and the current amount of this item available in stock. Since Hibernate works with plain, simple JavaBeans, all we need to do is to create getters and setters for every significant field (all fields are significant, in our case) and the default constructor.

package test.hibernate;

public class Product {
    private String id;
    private String name;
    private double price;
    private int amount;
    
    public String getId() {
        return id;
    }
    public void setId(String string) {
        id = string;
    }
    // default constructor and other 
    // getters/setters not shown for brevity
    // ...
}

Also, we override the toString() method. This will help us to follow the application flow using simple System.out.println(obj) calls:

public String toString() {
    return 
     "[Product] " + name + "(" + id +
     ") price=" + price + " amount=" + amount;
}

That's it, no more, no less. How Hibernate will know to persist the objects of this type, since Product doesn't implement any interface or extend any class? The answer is simple: Hibernate will work with any kind of Java object, as long as it follows JavaBeans convention.

Order

The next class we need to create is Order. It's even simpler than Product: it only contains ID, creation date, total price, and the Set of OrderItems of which this Order consists. Of course, create the getters and setters and default constructor.

package test.hibernate;

import java.util.Date;
import java.util.HashSet;
import java.util.Set;

 public class Order {
    private String id;
    private Date date;
    private double priceTotal;
    private Set orderItems = new HashSet();
    
    // Automatically set the creation time of 
    // this Order
    public Order() {
        this.date = new Date();
    }

    public String getId() {
        return id;
    }
    public void setId(String string) {
        id = string;
    }
    // other getters/setters not shown for 
    // brevity
    // ...
}

Override toString() as well. Don't forget to loop through orderItems! Download the complete source code to see the example.

OrderItem

This class is a little bit more complicated, but it's still fairly straightforward. Our business demands say that we need a certain amount of Products, which we will put into an Order. Those Products will automatically become OrderItems. It's a time for custom constructor.

package test.hibernate;

public class OrderItem {

    /**
    * Creates valid OrderItem. Automatically sets 
    * this OrderItem's price and corrects 
    * Product's stock availability amount.
    * 
    * @param order to which this OrderItem belongs
    * @param product from which this OrderItem is created
    * @param amount 
    */
    public OrderItem(Order order, 
                     Product product, 
                     int amount) {
                     
        this.order = order;
        this.product = product;
        this.amount = amount;
        product.setAmount(product.getAmount() - amount);
        this.price = product.getPrice() * amount;        
    }

    // we also need default constructor to keep
    // Hibernate happy
    /**
    * Empty constructor to conform to JavaBeans 
    * convention.
    *
    */
    public OrderItem() {
        // empty default constructor
    }

    // fields
    private String id;
    private Product product;
    private Order order;
    private String productId;
    private String orderId;
    private double price;
    private int amount;
    
    public String getId() {
        return id;
    }
    public String getProductId() {
        return product.getId();
    }
    public String getOrderId() {
        return order.getId();
    }
    // other getters/setters not shown
    // ...

    // convenient way to display this OrderItem
    public String toString() {
        return 
          "[OrderItem] id=" + id + " amount=" + 
          amount + " price=" + price + "(" + 
          product + ")";
    }
}

Now we have all of the classes that reflect the database structure. The only thing left unexplained is how we will put Products into an Order. Simply add the following method to the Order class:

/**
* Add a Product to this Order. Product 
* automatically becomes an OrderItem.
* The priceTotal is automatically updated.
* 
* @param p Product to add to this Order
* @param amount amount of products to add
*/
public void addProduct(Product p, 
                       int amount) {

   OrderItem orderItem = new OrderItem(this, 
                         p, amount);
                         
   this.priceTotal = this.priceTotal 
                     + p.getPrice() * amount;
                     
   this.orderItems.add(orderItem);
}

Pages: 1, 2

Next Pagearrow