Hibernate Your Data

Hibernate Your Data

Starting Up Hibernate

The basic usage pattern in our imaginary application is simple: we'll create a Product and then make it persistent (or in other words, save it), we'll search for and load an already persisted Product and make sure it's usable, and we'll update and delete Products.



Create And Persist A Product

Now we'll finally use Hibernate. The usage scenario is fairly simple:

  1. Create a valid Product.
  2. Obtain net.sf.hibernate.SessionFactory using net.sf.hibernate.cfg.Configuration at the start of the application.
  3. Open net.sf.hibernate.Session by calling SessionFactory#openSession().
  4. Save the Product and close the Session.

As we can see, there's no mention of JDBC, SQL, or anything similar. Very encouraging! The following sample follows the above-mentioned steps:

package test;



import net.sf.hibernate.Session;

import net.sf.hibernate.SessionFactory;

import net.sf.hibernate.Transaction;

import net.sf.hibernate.cfg.Configuration;

import test.hibernate.Product;



// use as

// java test.InsertProduct name amount price

public class InsertProduct {



    public static void main(String[] args) 

                        throws Exception {



        // 1. Build a Product

        Product p = new Product();

        p.setName(args[0]);

        p.setAmount(Integer.parseInt(args[1]));

        p.setPrice(Double.parseDouble(args[2]));



        // 2. Fire up Hibernate

        Configuration cfg = new Configuration()

                         .addClass(Product.class);

        SessionFactory sf = cfg.buildSessionFactory();



        // 3. Open Session

        Session sess = sf.openSession();



        // 4. Save Product and close Session

        Transaction t = sess.beginTransaction();

        sess.save(p);

        t.commit();

        sess.close();

    }

}

Let's run it for the first time! Insert 100 bottles of milk at 1.99 each by issuing java test.InsertProduct Milk 100 1.99. We get something similar to this:

Nov 23, 2003 9:05:50 AM net.sf.hibernate.cfg.Environment <clinit>

INFO: Hibernate 2.0.3

Nov 23, 2003 9:05:50 AM net.sf.hibernate.cfg.Environment <clinit>

INFO: hibernate.properties not found

Nov 23, 2003 9:05:50 AM net.sf.hibernate.cfg.Environment <clinit>

INFO: using CGLIB reflection optimizer

Nov 23, 2003 9:05:50 AM net.sf.hibernate.cfg.Environment <clinit>

INFO: JVM proxy support: true

Nov 23, 2003 9:05:50 AM net.sf.hibernate.cfg.Configuration addClass

INFO: Mapping resource: test/hibernate/Product.hbm.xml

Exception in thread "main" net.sf.hibernate.MappingException: 

Resource: test/hibernate/Product.hbm.xml not found

    at net.sf.hibernate.cfg.Configuration.addClass(Configuration.java:285)

    at test.FindProductByName.main(FindProductByName.java:24)

It doesn't work. Two lines are especially interesting:

INFO: hibernate.properties not found and

Resource: test/hibernate/Product.hbm.xml not found.

The INFO line indicates that we need a hibernate.properties configuration file, of course. That's the place where we configure which database we use, the username and password, and many other options. Use this provided sample to connect to the Hypersonic database mentioned before:

hibernate.connection.username=sa

hibernate.connection.password=

hibernate.connection.url=jdbc:hsqldb:/home/davor/hibernate/orders

hibernate.connection.driver_class=org.hsqldb.jdbcDriver

hibernate.dialect=net.sf.hibernate.dialect.HSQLDialect

Modify it as appropriate (e.g., you'll probably need to change hibernate.connection.url) and save in your classpath.

This was an easy one, but what is that test/hibernate/Product.hbm.xml resource? It's an XML file that defines how a Java object is persisted (mapped) to a database. In that file, we define into which database table the data goes, which field is mapped to which table column, how different objects relate to each other, etc. Let's take a look at Product.hbm.xml.

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE hibernate-mapping

    PUBLIC "-//Hibernate/Hibernate Mapping DTD//EN"

    "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">

    

<hibernate-mapping>

    <class name="test.hibernate.Product" 

           table="products">

              

        <id name="id" type="string" 

            unsaved-value="null">

            <column name="id" sql-type="char(32)" 

                    not-null="true"/>

            <generator class="uuid.hex"/>

        </id>

        <property name="name">

            <column name="name" sql-type="char(255)" 

                    not-null="true"/>

        </property>

        <property name="price">

            <column name="price" sql-type="double" 

                    not-null="true"/>

        </property>

        <property name="amount">

            <column name="amount" sql-type="integer" 

                    not-null="true"/>

        </property>        

    </class>

</hibernate-mapping>

It is very simple and understandable. A few details are especially interesting:

  • <class name="test.hibernate.Product" table="products"> says that we're mapping a class named test.hibernate.Product to the table products.

  • The <id> element and its child elements define the connection between our Java class and the database.

  • <property> elements define into which column each field goes, its type, name, etc.

The <generator class="uuid.hex"/> element is not quite understandable at first. Knowing that it is one of the child elements of <id>, its role become a little bit obvious: since our application doesn't know how its data will be persisted (and we're saying that all the time), we need a surrogate key, with no business meaning, to help Hibernate to manipulate objects. Newly created Products don't have that id, and Hibernate will create them for us. We chose to use UUID strings, but many different ID generators are provided (sequential, within some specific range, or even user assigned, etc.) and you can always write your own ID generator. See the documentation for details.

Now, create (copy and paste) the content of Product.hbm.xml and place the file into the same package as the test.hibernate.Product class (e.g., in the same directory where your Product.java file is placed) and re-run java test.InsertProduct Milk 100 1.99. Now we see much more logging information and ... nothing more! Is it working correctly? Add System.out.println(p) just before Session sess = sf.openSession(); and after sess.close() to see what's going on with our Product. Re-run. You'll see something similar to this (that funny ID number will surely be different):

[Product] Milk(null) price=1.99 amount=100

[Product] Milk(40288081f907f42900f907f448460001) price=1.99 amount=100

Hibernate created the Product's id for us! Let's see if the Product is stored in our database. Issue select * from products. The database returns something similar to:

ID                              |NAME  |PRICE |AMOUNT |

40288081f907f42900f907f448460001|Milk  |1.99  |100    |

The Product information is successfully inserted into our database and we haven't written a single line of SQL!

Insert some other products, such as bread, coffee, beer, etc., to have some data to work with in this tutorial.

Find And Load Products

Finding and loading already persisted objects is very simple with Hibernate. Using its query language we can easily fetch an object (or set of objects) by its ID, name, or some other property. We can fetch the complete object or just some of its properties. Hibernate will take care of the rest, and at the end we'll have completely useful hierarchy of objects. Let's take a look at the test.FindProductByName class.

package test;



import java.util.List;



import net.sf.hibernate.Hibernate;

import net.sf.hibernate.Session;

import net.sf.hibernate.SessionFactory;

import net.sf.hibernate.cfg.Configuration;

import test.hibernate.Product;



// use as 

// java test.FindProductByName name

public class FindProductByName {



    public static void main(String[] args) throws Exception {

        // query to issue

        String query =

            "select product from product "

            + "in class test.hibernate.Product "

            + "where product.name=:name";



        // search for what?

        String name = args[0];



        // init

        Configuration cfg = new Configuration()

                           .addClass(Product.class);



        SessionFactory sf = cfg.buildSessionFactory();



        // open session

        Session sess = sf.openSession();

        

        // search and return

        List list = sess.find(query, name, 

                              Hibernate.STRING);



        if (list.size() == 0) {

            System.out.println("No products named " 

                               + name);

            System.exit(0);

        }

        Product p = (Product) list.get(0);

        sess.close();

        System.out.println("Found product: " + p);

    }

}

We have several interesting points in FindProductByName:

  • There's a query string with a where clause. This is very similar to standard SQL.

  • We initialize Hibernate in the same way as in our first example. This time, we have the configuration and mapping files.

  • sess.find() executes the query and sets the provided product name as the search argument of type Hibernate.STRING.

  • As the result, we get a java.util.List full of found Products.

  • With Product p = (Product) list.get(0); we fetch the found objects in the usual way, with casting.

Issue java test.FindProductByName Milk and see what's shown in the console.

Note: Queries are case-sensitive, so searching for lowercase milk won't return any results. Use the lower() or upper() SQL functions to enable case insensitive searches. In that case, we would have where lower(product.name)=lower(:name) in our query string. See the documentation for details on queries. Also, if you don't want all the INFO logging information displayed, tweak log4j.properties and set all log levels to warn.

Update And Delete Products

By now you should have basic understanding of how Hibernate works, so let us make long examples shorter by showing only the important parts.

To increase the prices of all Products by 10% percent in a single transaction, we would write something like this:

double percentage = Double.parseDouble(args[0])/100;



sess = sf.openSession();

Transaction t = sess.beginTransaction();



// list contains Products

Iterator iter = list.iterator();

while (iter.hasNext()) {

    Product p = (Product) iter.next();            

    p.setPrice(p.getPrice() * (1 + percentage));

    sess.saveOrUpdate(p);      

}

t.commit();

sess.close();

And finally, to delete a Product, we would, of course, call sess.delete(product). Don't forget to commit() the Transaction if autocommit is turned off for your database.

Now we have gone through all of the basic operations -- create, read, update, and delete -- for a single object. It does look interesting, but things are getting even better. We'll now learn how to manipulate a collection of objects without writing a single SQL statement. All of the magic will be done through the mapping files.

Orders, OrderItems

Manipulating objects on one-by-one basis will definitely save as some time, but what we really want are cascading loads and updates. We'll now see how to do that.

We need to examine Orders and OrderItems in parallel. As mentioned before, we add a Product to an Order and it then becomes an OrderItem. Order internally keeps a set of OrderItems. What we want is to save Order and have Hibernate do the rest: save OrderItems and to update the stock availability (amount) of the added Products. Sounds complicated, but it is actually very simple. Hibernate knows how to deal with related objects in one-to-one, one-to-many, many-to-one, and many-to-many fashion. We'll start with the mapping files.

Order.hbm.xml

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE hibernate-mapping

    PUBLIC "-//Hibernate/Hibernate Mapping DTD//EN"

    "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">

<hibernate-mapping>

    <class name="test.hibernate.Order" table="orders">

        <id name="id" type="string" unsaved-value="null" >

            <column name="id" sql-type="char(32)" not-null="true"/>

            <generator class="uuid.hex"/>

        </id>

        <property name="date">

         <column name="order_date" 

                 sql-type="datetime" not-null="true"/>

        </property>

        <property name="priceTotal">

        <column name="price_total" 

                sql-type="double" not-null="true"/>

        </property>

        

        <set name="orderItems" table="order_items" inverse="true"  cascade="all">

            <key column="order_id" />

            <one-to-many class="test.hibernate.OrderItem" />

        </set>

        

    </class>

</hibernate-mapping>

This mapping file is quite understandable, except the last element, <set>. This represents connections between different classes; in our case, those are Order and OrderItem. The attributes and child elements are quite understandable: a field of type Set, named orderItems (see the Order source code above), contains objects of the type test.hibernate.OrderItem, as explained by <one-to-many> child element. Those objects are persisted in the order_items table, where the order_id column contains keys for OrderItem type of objects.

The cascade="all" attribute is a very important one. It explains how Hibernate should act while manipulating connected objects. In our specific situation, when an Order is created, we definitely want all of its OrderItems to be created as well, and, of course, when an Order is deleted, we also want all of its OrderItems to be deleted. There are three more options cascade attribute can hold, none, save-update, and delete, and we'll see how to use them in the following example.

OrderItem.hbm.xml

This object is an interesting one. Its instances are created automatically within Order, and basically don't have life outside of it. However, we need them, since they represent the Products at the time Order was created. So, if a Product's price is changed, we definitely don't want all the appropriate OrderItems' and therefore Orders' prices to be changed. But what we want is to update the stock availability of a Product whenever an OrderItem is created. And finally, when an Order is deleted, its OrderItems are deleted, as well, but we must not touch the Products! Sounds complicated, especially when all of those SQL statements need to be written. But Hibernate compresses all of them into two lines in the mapping file!

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE hibernate-mapping

    PUBLIC "-//Hibernate/Hibernate Mapping DTD//EN"

    "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">



<hibernate-mapping>

    <class name="test.hibernate.OrderItem" 

             table="order_items">

        <id name="id" type="string" unsaved-value="null" >

            <column name="id" sql-type="char(32)" 

                       not-null="true"/>

            <generator class="uuid.hex"/>

        </id>

        <property name="orderId" insert="false" 

                     update="false">

            <column name="order_id" sql-type="char(32)" 

                       not-null="true"/>

        </property>

        <property name="productId" insert="false" 

                     update="false">

            <column name="product_id" sql-type="char(32)" 

                       not-null="true"/>

        </property>

        <property name="amount">

            <column name="amount" sql-type="int" 

                       not-null="true"/>

        </property>

        <property name="price">

            <column name="price" sql-type="double" 

                       not-null="true"/>

        </property>

        <many-to-one name="order" 

                 class="test.hibernate.Order" 

                 column="order_id" />

        <many-to-one name="product" 

                 class="test.hibernate.Product" 

                 cascade="save-update" 

                 column="product_id"/>

    </class>

</hibernate-mapping>

We know all about the <id> and <property> elements by now, but <many-to-one> is a new one. It's fairly simple. The first use of the <many-to-one> element indicates that OrderItem's field named order is of type test.hibernate.Order and is referenced through the order_id column from the table order_items (see the table attribute of the element class). The second many-to-one element is similar to the first one, except that it has cascade="save-update" attribute. It's explained before what it defines. In this case, we say that Hibernate should propagate changes on Products only when an OrderItem is saved (created) or updated (changed), and not on delete. So the above-mentioned concerns about complicated SQL statements are compressed in one single attribute! Now beat that!

Usage Examples

Create an Order. In this example, we create and persist an Order. Run this example more than once to see how Products' amounts change after each successful Order creation.

// ...

Configuration cfg = new Configuration()

                    .addClass(Product.class)

                    .addClass(Order.class)

                    .addClass(OrderItem.class);



// ...

Order order = new Order();

order.addProduct(milk, 3);

order.addProduct(coffee, 5);



// ...

sess = sf.openSession();

Transaction t = sess.beginTransaction();

sess.save(order);

t.commit();

sess.close();



System.out.println(order);

// ...

Find Orders within a price range. In this example, we show how to use a query with two parameters. Hibernate correctly loads Orders with appropriate OrderItems and Products.

// ...

String query = "select o from o "

    + "in class test.hibernate.Order "

    + "where o.priceTotal > :priceTotalLower "

    + "and o.priceTotal < :priceTotalUpper";



// ...                

Query q = sess.createQuery(query);

q.setDouble("priceTotalLower", 

             Double.parseDouble(args[0]));

q.setDouble("priceTotalUpper", 

             Double.parseDouble(args[1]));



List list = q.list();

// ...

sess.close();

// ...

Delete Orders within a price range. This is an important example. Here we can see how intelligent a tool Hibernate is. As mentioned above, when deleting an Order, its OrderItems need to be deleted, but the Products must not be changed. After this example, check your database to ensure that products are intact.

 // ...

String query = "select o from o "

    + "in class test.hibernate.Order "

    + "where o.priceTotal > :priceTotalLower "

    + "and o.priceTotal < :priceTotalUpper";



Transaction tx = sess.beginTransaction();

sess.delete(query, 

    new Object[]{new Double(args[0]), 

                 new Double(args[1])}, 

    new Type[]{Hibernate.DOUBLE, 

               Hibernate.DOUBLE}

           );       

tx.commit();

sess.close();

Conclusion

This article shows how powerful Hibernate is. You've learned how easy it is to persist any kind of Java object, to manipulate a hierarchy of objects, handle collections, and work with transactions. But the scope of Hibernate's functionality is much wider. It handles full transactions with commit and rollback, inheritance, a few types of collections, and offers very powerful object-oriented query language, HQL, which supports associations and joins, polymorphism, subqueries, etc. Your next step is to read the Hibernate Reference Documentation and to start using Hibernate in your day-to-day work.

Resources

  • Source code used in this article

  • Hibernate home page, www.hibernate.org: Here you will find the most recent Hibernate distribution, documentation, other tutorials, etc.

  • O/R Mapping tools comparison: visit this page if you want to compare Hibernate to other O/R mapping tools.

  • "The Fundamentals of Mapping Objects to Relational Databases": O/R Mapping tools theory.

  • HypersonicSQL: Open source pure-Java SQL database.

  • Log4j: Logging lib for everyone.

Davor Cengija is an IT consultant for TIS PU, a Zagreb, Croatia based company specialized in business integration tasks.


Return to ONJava.com.

Prev  [1] [2] 

Close    To Top
  • Prev Article-Java:
  • Next Article-Java:
  • Now: Tutorial for Web and Software Design > Java > JDOnJDBCnSQLJ > Java Content
    Photoshop Tutorial
     

    Special Effect

      3D Effect
      Photoshop Articles
    Programming Tutorial
     

    C/C++ Tutorial

      Visual Basic
      C# Tutorial
    Database Tutorial
     

    MySQL Tutorial

      MS SQL Tutorial
      Oracle Tutorial
    Geek Tutorial
     

    Blogging Tutorial

      RSS Tutorial
      Podcasting Tutorial
    Graphic Design Tutorial
      Coreldraw Tutorial
      Illustrator Tutorial
      3D Tutorials
    Webmaster Articles
     

    Domain Service

      Web Hosting
      Site Promotion
    Java Tutorial/ Articles
     

    Java Servlets

      JavaEE Tutorial
     

    JavaBeans Tutorial

    XML Tutorial/ Articles
     

    XML Style

      AJAX Tutorial
      XML Mobile
    Flash Tutorial/ Articles
     

    Flash Video

      Action Script
      Flash Articles
    OS Tutorial/ Articles
      Linux Tutorial
      Symbian Tutorial
      MacOS Tutorial
    Personal Tech
      Hardware Tutorial
      Software Tutorial
      Online Auction