Chapter 7. Hibernate Core

In this chapter we examine Hibernate and how it implements the object-relational mapping techniques described in the previous chapters. We briefly survey its history, discuss how it is used, and show several end-to-end examples of its use.

Background

Hibernate is an open-source, object-relational mapping framework that is available from and supported through www.hibernate.org. Hibernate is relatively lightweight, very powerful, and in the latest (3.2) version a truly general persistence framework that will persist POJOs, XML, or arbitrary Java maps.

Hibernate currently comes in two forms. The first, Hibernate Core (which is the subject of this chapter, and which we will simply refer to as Hibernate), is the original mapping framework API. Hibernate Annotations is the second form—it supports JDK 5.0 annotations and is closely linked to the Hibernate Entity Manager, an implementation of the JPA API built with the Hibernate engine. In both forms, Hibernate is rapidly becoming a de facto standard for persistence across the Java industry. Recent statistics from SourceForge show that Hibernate is being downloaded at a rate between 1,000 and 5,000 times per day. This download rate has been sustained for well over three years, and shows every sign of increasing.

In this chapter we do not show the details of either Hibernate Annotations or Hibernate Entity Manager, because we are focusing on the basic capabilities of Hibernate Core and comparing it to the other frameworks. And because Chapter 8 evaluates JPA in the context of openJPA, we recommend that you read more about Hibernate’s Entity Manager [Hibernate 1] for more details on their implementation approach and specific features.

Hibernate Core provides both simplicity and flexibility for developers who need object-relational mapping. It can perform most mapping tasks in a simple, configuration-driven way that does not require the developer to write any SQL code at all. However, if you want to write your own SQL, or use stored procedures for loading and storing data, Hibernate supports that as well. Likewise, it can provide simple connection pooling and caching that would be effective in a JAVA SE environment, or it can integrate with application servers and third-party caching solutions to function effectively in a Java EE environment as well.

Type of Framework

Hibernate is a full object-relational mapping framework that implements the Object Mapper pattern. Although you could implement other pattern types with Hibernate, the fact that it directly supports Object Mapper means that this is the most commonly utilized persistence pattern associated with Hibernate.

History

The following timeline summarizes some of the major events in the evolution of the Hibernate framework. For more information on the background and history of Hibernate, refer to Chapter 1, “A Brief History of Object-Relational Mapping.”

  • November 2001—Initial SourceForge project opened.

  • Mid-2002—Hibernate 1.0 released.

  • June 2003—Hibernate 2.0 released.

  • October 2003—Hibernate developers hired by JBoss.

  • Feb 2005—Hibernate 3.0 released.

Architectural Overview

Hibernate is a fully featured yet small open-source framework for implementing object-relational mapping. It is compliant with most appropriate Java standards in this space and will work within JAVA SE, within most application servers, and within frameworks like Spring.

Standards Adherence

Although there is a version of Hibernate (Hibernate Entity Manager) that fully implements the JPA specification, many Hibernate developers use a POJO (Plain Old Java Objects) model supported through the Hibernate Core API. So strictly speaking, Hibernate Core does not represent or adhere to a specific standard.

However, Hibernate provides a simple API for object-relational mapping that maps Java classes to database tables so that it can be used both within a strict Java SE environment, in which case it handles its own transaction management against a single database, and within a JEE container, allowing the container to manage transactions and issues such as database pooling. Hibernate is compatible with most vendors’ JDBC drivers, and will also support most JEE containers.

Platforms Required

Hibernate Core, being a pure Java solution, will run on any hardware platform supporting a Java SE environment. Hibernate Annotations requires Java 5 because it relies upon Java 5 Annotations.

Other Dependencies

Hibernate has a remarkably small code base. The Hibernate Core runtime JAR file is only 2.1MB, and Hibernate relies on only a small set of open-source projects that provide underlying functionality. These include Apache Common Collections and Common logging, ANTLR, DOM4J, ASM (the ObjectWeb open-source bytecode manipulation framework), and the CGLib code generation library. These additional open-source libraries are included in the download for Hibernate. Hibernate requires a JDBC driver for whatever database will be used with Hibernate, which can be obtained from the database vendor.

Hibernate is commonly used with the Spring framework (www.springframework.org) for Aspect Oriented Programming and Dependency Injection, and many Spring projects use Hibernate as their persistence engine. When you use Spring with Hibernate, it simplifies transaction management and also simplifies Hibernate’s configuration. However, in this chapter we want to focus on the persistence features of Hibernate and be able to compare it directly with other frameworks like OpenJPA and iBATIS, so we will not use Spring in our examples.

Vendors and Licenses

Hibernate is controlled by the JBoss Group. Although the implementation is handled through the open-source process, many of the key developers are JBoss Group employees, and the JBoss Group certainly sets the priorities of the project. Hibernate has been released under the Lesser Gnu Public License (LGPL). This means that changes to the source code of Hibernate must be made available to the original authors of Hibernate. Given that it is an open-source product, you can obtain production-level downloads directly from www.hibernate.org.

Available Literature

There are many good sources of material for help in understanding Hibernate. Several of the best are listed in Table 7.1.

Table 7.1. Available Literature for Hibernate

Title

Source

Description

Java Persistence with Hibernate [Bauer]

Manning Publications Co.

Excellent introductory resource written by the lead developer of Hibernate.

Hibernate Reference Guide [Hibernate 2]

www.hibernate.org/5.html

Comprehensive reference documentation available in HTML and PDF form.

Wiki [Hibernate 3]

www.hibernate.org/37.html

Excellent for platform-specific notes and information on new undocumented features.

User Mailing Lists [Hibernate 4]

www.hibernate.org/20.htmlh

Resource for advanced questions and debugging.

Available Literature for Hibernate

A.7.1

Hibernate Simplifies Inheritance Mapping

www.ibm.com/developerworks/java/library/j-hibernate/

Article illustrating inheritance with Hibernate

Available Literature for Hibernate

A.7.2

Using Hibernate to Persist Your Java Objects to IBM DB2 Universal Database

www.ibm.com/developerworks/db2/library/techarticle/0306bhogal/0306bhogal.html

This article gives a simple Hibernate overview, illustrating the use of Hibernate with IBM DB2.

Available Literature for Hibernate

A.7.3

Using Spring and Hibernate with WebSphere Application Server

www.ibm.com/developerworks/websphere/techjournal/0609_alcott/0609_alcott.html

This article shows the proper setting to configure when using Hibernate and Spring with WebSphere Application Server.

Programming Model

The Hibernate programming model is simple because it is designed to keep out of the programmer’s way. By abstracting the relational details away from the programmer, and moving most of the work of object-relational mapping into XML configuration files, it provides the programmer with a simple, POJO-based approach that makes mapping as easy (in most cases) as calling a method that takes a POJO as a parameter to make it persistent; or receiving a POJO as a method return value to retrieve it from the database.

The Hibernate programming model involves a POJO and an object called Session, which manages the life-cycle of a POJO. A POJO can be in one of three states:

  • Transient—An object that is not yet associated with a Session, and therefore is not persistent.

  • Persistent—An object that has just been saved or loaded within the scope of an active Session context.

  • Detached—An object that has been saved or loaded, but the associated Session context has been closed.

A Transient and Detached object can be made Persistent by being saved, loaded, or merged into an active Session; this cycle can repeat as often as necessary.

Initialization

Hibernate has a very simple initialization mechanism based on the idea of a Hibernate Session and a Hibernate Configuration. A Configuration is an object that reads a Hibernate Configuration file—the contents of this file determine the configuration options such as the JDBC driver used, how caches and connection pools are configured, and so forth. Configurations are used to build Session Factories, which are then used by methods that use Hibernate persistence (like our service implementations) to obtain Hibernate Sessions. An example of a Session Factory derived from a Configuration is shown in Listing 7.1.

Example 7.1. Setting Up a Session Factory

<LINELENGTH>90</LINELENGTH>
SessionFactory sessionFactory = new Configuration()
    .configure ("/persistence/my.cfg.xml")
    .buildSessionFactory();

The Configuration obtains its information from a configuration file usually named hibernate.cfg.xml. This file can be located anywhere, but it is customarily located at the root of the classpath. A sample, very bare-bones configuration file suitable for use in a J2SE environment is shown in Listing 7.2.

Example 7.2. Hibernate Configuration File for Derby

<LINELENGTH>90</LINELENGTH>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
  "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
  "hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<property name="current_session_context_class">thread</property>
<property name="hibernate.connection.driver_class">
    org.apache.derby.jdbc.EmbeddedDriver
</property>
<property name="hibernate.connection.url">
    jdbc:derby:c:eclipseBookDB;create=true;
</property>
<property name="hibernate.dialect">
    org.hibernate.dialect.DerbyDialect
</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>
<!-- Mapping files -->
</session-factory>
</hibernate-configuration>

In most cases, the code will be encapsulated into a helper class that is usually named (by convention) HibernateUtil. A simple HibernateUtil class is provided in the documentation of the Hibernate framework. An example of how the HibernateUtil class is used to obtain a Hibernate Session object (and how the Session object is used for transaction management) is shown in Listing 7.3.

Example 7.3. HibernateUtil Used for Initialization

<LINELENGTH>90</LINELENGTH>
Session session = HibernateUtil.getSessionFactory().getCurrentSession();
session.beginTransaction();
AbstractCustomer retrievedCustomer = (AbstractCustomer)session.load(
    AbstractCustomer.class, new Integer(100)
);
session.getTransaction().commit();

In Listing 7.3 we demonstrated how to use the Derby JDBC driver from within Hibernate. In Listing 7.4, we show how to use an application DataSource with JNDI name jdbc/Derby/DataSource.

Example 7.4. Hibernate Configuration File for DataSource

<LINELENGTH>90</LINELENGTH>
<property name="hibernate.connection.datasource">
    java:comp/env/jdbc/Derby/DataSource
</property>
<property name="hibernate.connection.username">admin</property>
<property name="hibernate.connection.password">password</property>

This example set of properties (which would replace the hibernate.connection.driver.class and hibernate.connection.url properties in Listing 7.2) inform the hibernate SessionFactory to use a DataSource, and provide the JNDI name for the datasource, as well as a username and password for authentication to the database.

Connections

Because Hibernate supports a full ORM approach, it uses a transparent connection model. It does not directly support an explicit connection model, meaning that creating a JDBC connection is not one of the standard steps in using Hibernate. When used with an application DataSource as shown previously, Hibernate will use whatever connection pooling is provided by the underlying DataSource. When used with Database drivers that do not support connection pooling, Hibernate also supports third-party connection pooling approaches, such as the C3P0 connection and statement pooling classes. Finally, Hibernate also allows the user to provide his own JDBC Connection object to a Hibernate Session for its use, although the use of this feature is discouraged.

Transactions

Hibernate has been designed to work either inside or outside of a Java EE application server. As such, it supports externally managed JTA transactions, and also can manage its own transaction demarcation with the org.hibernate.Transaction API. If no explicit external transaction management is specified or the Hibernate API is not used, it will default to a “pseudo” autocommit for each Hibernate statement. We say “pseudo” because Hibernate does not use the autocommit mode for the underlying database driver, but instead invokes each Hibernate statement its own transaction.

The example in Listing 7.5 shows grouping two updates (setting the business partner attribute to false, and updating that customer’s open order status to SUBMITTED) within a transaction using Hibernate’s org.hibernate.Transaction API.

Example 7.5. Using Transactions in Hibernate

<LINELENGTH>90</LINELENGTH>
// note -- assumes customer #100 is in the database and has an open
// order.
Session session = HibernateUtil.getSessionFactory().getCurrentSession();
session.beginTransaction();
BusinessCustomer firstBusinessCustomer = (BusinessCustomer)
   session.load(BusinessCustomer.class, new Integer(100));
firstBusinessCustomer.setBusinessPartner(false);
session.save(firstBusinessCustomer);
Order openOrder = firstBusinessCustomer.getOpenOrder();
openOrder.setStatus(Status.SUBMITTED);
firstBusinessCustomer.setOrder(null);
session.save(firstBusinessCustomer);
session.save(openOrder);
session.getTransaction().commit();

Note that in this example we had to perform a session.save() on both the customer and the order, because we disconnected the order from the customer when we set the Customer’s order to null as part of changing the status to submitted (meaning the Customer had no current open order). We discuss the save() method throughout this section.

As stated earlier, Hibernate will work with JTA (the Java Transaction API) and you can configure Hibernate to use the JTA within the context of an application server as its transaction manager. You have two choices for using JTA with Hibernate:

  • You can let Hibernate use JTA as its underlying transaction manager while still using the Hibernate API for transaction management. This has the advantage of being consistent with programs written that run outside of an application server.

  • You can manage transactions external to hibernate with the JTA javax.transaction.UserTransaction API. This has the advantage that Hibernate persistence can be tied together in a JTA transaction with operations on other resources like JMS queues.

In either case, setting up Hibernate to use JTA is the same—it simply requires a change to the Hibernate configuration file. In particular, you need to set three elements of this file:

  • The property hibernate.transaction.factory_class must be set to org.hibernate.transaction.JTATransactionFactory.

  • You must inform Hibernate of the location of the JTA UserTransaction—for example, in WebSphere this object is found in the JNDI name space at java:comp/UserTransaction.

  • The property hibernate.transaction.manager_lookup_class must be set to the option that is appropriate for your application server environment. For instance, in WebSphere you would set this value to WebSphereExtendedJTA TransactionLookup.

Note that as of Hibernate 3.2, there was a bug introduced into Hibernate that will not allow you to use the WebSphereExtendedJTATransactionLookup class (which is the method supported by IBM). The workaround for this bug was to use the WebSphereTransactionManagerLookup class instead, which is not supported by IBM for external use.

A section of a sample configuration file that illustrates setting these elements within this known limitation is shown in Listing 7.6.

Example 7.6. Hibernate Configuration File for JTA

<LINELENGTH>90</LINELENGTH>
<property name="hibernate.transaction.factory_class">
    org.hibernate.transaction.JTATransactionFactory
</property>
<property name="hibernate.transaction.manager_lookup_class">
    org.hibernate.transaction.WebSphereExtendedJTATransactionLookup
</property>
<property name="jta.UserTransaction">
    java:comp/UserTransaction
</property >

When using Hibernate with JTA, you must also configure it to use DataSources obtained from the application server with JNDI, as was covered previously.

Hibernate supports all the standard JDBC isolation levels, either through the connections it manages on its own, or through the connections provided by the Application Server. For instance, if you are using Hibernate’s internal connection pooling, then to set the isolation level on that connection to be “Read Uncommitted” you would use the following property in the Hibernate configuration file:

<LINELENGTH>90</LINELENGTH>
<property name="hibernate.connection.isolation">1</property>

The integer 1 refers to the isolation level value for TRANSACTION_READ_UNCOMMITTED that is found as a constant having that name in the class java.sql.Connection. Note that this should be done only if application isolation is being managed directly, and not through the capabilities of an application server.

Create

Creating a new object in Hibernate is really as simple as creating the POJO with the new operator and then using the Hibernate Session save() operation to make the object persistent. The example in Listing 7.7 shows how to create a new Customer object in Hibernate. Note that this example sets the primary key (the customer ID) manually rather than using one of Hibernate’s automatic key-generation strategies.

Example 7.7. Create a New Customer

<LINELENGTH>90</LINELENGTH>
BusinessCustomer customer = new BusinessCustomer(
    false, false, "Default Business Partner", "Harry's fast foods",
    null, null
);
customer.setCustomerId(42);
Session session = HibernateUtil.getSessionFactory().getCurrentSession();
session.beginTransaction();
session.save(customer);
session.getTransaction().commit();

This approach to setting the key manually is not possible in our common code examples, given the default schema we specified as one of the endpoints in Chapter 3. To make this approach work with the common code examples, you must remove the key generation annotations from the Derby primary key definition for Order. We will discuss this subject again later in the “Keys” section.

The example in Listing 7.8 shows how to take the previously created customer and create an Order that belongs to that customer.

Example 7.8. Create a New Order for the Previously Created Customer

<LINELENGTH>90</LINELENGTH>
// This assumes customer number 100 (created in the previous example)
// is in the database
Session session = HibernateUtil.getSessionFactory().getCurrentSession();
session.beginTransaction();
BusinessCustomer retrievedCustomer =(BusinessCustomer)session.load(
    BusinessCustomer.class, new Integer(100)
);
Order newOrder = new Order();
newOrder.setCustomer(retrievedCustomer);
newOrder.setOrderId(247);
newOrder.setStatus(Status.OPEN);
retrievedCustomer.setOpenOrder(newOrder);
session.getTransaction().commit();

Note that in Listing 7.8 we did not need to explicitly save the Order—this is because by changing the openOrder attribute of the Customer, we rendered this object “dirty,” and Hibernate knew to make persistent any objects attached to that object. Hibernate also allows you to specify insert statements using HQL, which we describe in the next section.

Retrieve

In an earlier section we showed how you can use the Session.load() method to load a Customer from the database given a particular primary key. Another option you can use that is similar to Session.load() is Session.get(). It works the same as Session.load() except instead of throwing an exception when a row is not found, it returns null instead.

A more interesting retrieval mechanism in Hibernate is the ability of programmers to use the HQL (Hibernate Query Language) to specify queries on mapped objects. For instance, the code to retrieve all the Customers that are “active” (that is, that have an open order) is shown in Listing 7.9.

Example 7.9. Retrieve Active Customers with HQL

<LINELENGTH>90</LINELENGTH>
Session session = HibernateUtil.getSessionFactory().getCurrentSession();
session.beginTransaction();
List activeCustomers = session.createQuery(
    "from org.pwte.example.domain.AbstractCustomer as cust
     where cust.openOrder != null"
).list();
session.getTransaction().commit();

The HQL Query used in this example (from org.pwte.example.domain. AbstractCustomer as cust where cust.openOrder != null) demonstrates several of the relevant features of HQL. The simplest HQL query is simply from classname, which retrieves all instances of a mapped class name from the database. There is an optional select clause that precedes the from clause that allows you to select individual properties of the mapped object or objects contained within the mapped object as an array or a list. Likewise, the where clause allows the specification of which objects to instantiate as part of the list based on the values of the attributes of the mapped object. HQL also supports most standard SQL concepts such as joins, functions, group by, and order by.

For most purposes, though, you will tend to use the power of Hibernate’s built-in support for mapping relations between tables to associations between objects, rather than building custom queries in HQL. For instance, the code in Listing 7.10 shows how simple it is to retrieve all the Orders associated with a Customer.

Example 7.10. Retrieving Orders for a Customer

<LINELENGTH>90</LINELENGTH>
Session session=HibernateUtil.getSessionFactory().openSession();
session.beginTransaction();
BusinessCustomer retrievedCustomer = (BusinessCustomer) session.load(
    BusinessCustomer.class, new Integer(43)
);
Set<Order> orders = retrievedCustomer.getOrders();
session.getTransaction().commit();
session.close();

As you can see, Hibernate handles the hard part (querying the tables) for you—you just traverse the existing object relationships established in your POJOs. Likewise, obtaining the singleton open order associated with a customer is a just as simple, as Listing 7.11 shows.

Example 7.11. Retrieving the Open Order for a Customer

<LINELENGTH>90</LINELENGTH>
Session session=HibernateUtil.getSessionFactory().openSession();
session.beginTransaction();
BusinessCustomer retrievedCustomer = (BusinessCustomer) session.load(
    BusinessCustomer.class, new Integer(100)
);
Order openOrder = retrievedCustomer.getOpenOrder();
session.getTransaction().commit();
session.close();

Finally, Hibernate also supports the use of Custom SQL for querying. You can define a SQL (or HQL) query in the mapping document, then name it, and then use that named query. For instance, if we needed to be able to classify Products by some part of the product description, then one way to accomplish that is to place a query in the mapping document for Product, as shown in Listing 7.12.

Example 7.12. Creating a Named Query in a Hibernate Configuration File

<LINELENGTH>90</LINELENGTH>
<sql-query name="classifiedProducts">
    <return alias="product" class="org.pwte.example.domain.Product"/>
    SELECT product.PRODUCT_ID AS {product.productId},
           product.PRICE AS {product.price},
           product.DESCRIPTION AS {product.description}
    FROM PRODUCT product
    WHERE product.description LIKE :namePattern
</sql-query>

You can then use that query with the code given in Listing 7.13, which creates a List of all Product instances that have the word “Gadgets” in their description.

Example 7.13. Using named queries in Hibernate

<LINELENGTH>90</LINELENGTH>
List products = session.getNamedQuery("classifiedProducts")
        .setString("namePattern", "%Gadgets%")
        .setMaxResults(50)
        .list();

Not only does Hibernate support Custom SQL for specifying queries that can then be mapped to the objects defined in a Hibernate mapping file, but Hibernate also supports custom SQL in many other ways. In particular, if you want to specify the SQL to be used for the update, insert, or deletion of an object, that can be specified in the mapping file also. This is done through the use of the <sql-query>, <sql-insert>, <sql-update>, and <sql-delete> tags in the Hibernate mapping file. We show the use of these tags later, in the section “Stored Procedures.”

Finally, a query mechanism that is truly unique to Hibernate among the ORM products we are evaluating is the Criteria mechanism. The Criteria mechanism allows the developer to construct queries “on the fly” through an implementation of the Interpreter pattern, which you can find in Patterns: Elements of Reusable Object-Oriented Software [Gamma 94]. There are two primary classes involved in this interface: the Criteria class and the Restriction class. A Restriction represents a particular comparison or statement to be evaluated against an object. For instance, if you wanted to see whether a Product instance had a particular description (for example, “Widget”), you would use the following method to create an equivalent Restriction that asks whether the attribute name matches the value Widget:

<LINELENGTH>90</LINELENGTH>
Restriction.eq("description", "Widget")

There are methods in the Restriction class that allow for comparisons against null, evaluations of whether an attribute is greater than or less than another value, and String comparisons to see whether an object’s attribute contains a String, as well as others.

Restrictions are joined together to form Criteria, which are evaluated against a persistent class. So, to find out whether you have a particular AbstractCustomer matching the name “Kyle Brown,” you would need the statement shown in Listing 7.14.

Example 7.14. Abstract Customer Criteria Example

<LINELENGTH>90</LINELENGTH>
AbstractCustomer customer = (AbstractCustomer)
    session.createCriteria(AbstractCustomer.class)
    .add(Restrictions.eq("name", "Kyle Brown"))
    .uniqueResult();

Update

One of the ramifications of using a full object-relational mapping tool is that in some cases, making a change to only a single column in a single row of a database may require navigating through several layers of object relationships to first identify the object corresponding to that row before you can make the change. A good example of this is changing the quantity of a line item in a specific order held by a specific customer. The code that’s needed to change the line item’s quantity is simple—you just invoke the setQuantity() method on the appropriate line item. Identifying that line item can be involved, as the code in Listing 7.15 demonstrates.

Example 7.15. Changing a LineItem Quantity

<LINELENGTH>90</LINELENGTH>
Session session=HibernateUtil.getSessionFactory().openSession();
session.beginTransaction();
BusinessCustomer retrievedCustomer = (BusinessCustomer)session.load(
    BusinessCustomer.class, new Integer(100)
);
Order openOrder = retrievedCustomer.getOpenOrder();
// find Line Item for the specific product having ID = 100
Iterator<LineItem> lineItems = openOrder.getLineitems().iterator();
while (lineItems.hasNext()) {
    LineItem item = lineItems.next();
    if (item.getLineItemId().productId == 100) {
        item.setQuantity(100);
    }
}
session.getTransaction().commit();
session.close();

A particular issue with this approach is that depending on the settings of lazy or eager loading, this code may invoke a number of different SQL queries—so this approach should be used with careful attention to the performance details. Finally, some changes are truly trivial, such as that shown in Listing 7.16 to change an open order in a customer to null.

Example 7.16. Setting an Open Order to null in a Customer

<LINELENGTH>90</LINELENGTH>
Session session=HibernateUtil.getSessionFactory().openSession();
session.beginTransaction();
BusinessCustomer retrievedCustomer = (BusinessCustomer) session.load(
    BusinessCustomer.class, new Integer(200)
);
retrievedCustomer.setOpenOrder(null);
session.getTransaction().commit();
session.close();

Hibernate also allows you to use HQL Update statements to do bulk updates.

Delete

Hibernate supports both explicit and cascaded deletes. The simplest way to delete an object is simply through the Session.delete() method, which is roughly the reverse of Session.save(). However, deleting a single object is often not as simple as that. Consider the case of removing a LineItem from an Order. First, you have the issue of navigating to the particular LineItem. After you have identified the particular LineItem you are interested in, you can remove it from the collection that contains it, as Listing 7.17 shows.

Example 7.17. Removing a Line Item

<LINELENGTH>90</LINELENGTH>
Session session=HibernateUtil.getSessionFactory().openSession();
session.beginTransaction();
BusinessCustomer retrievedCustomer = (BusinessCustomer)session.load(
    BusinessCustomer.class, new Integer(100)
);
Order openOrder = retrievedCustomer.getOpenOrder();
// find Line Item for the specific product having ID = 100
LineItem targetItem = null;
Iterator<LineItem> lineItems = openOrder.getLineitems().iterator();
while (lineItems.hasNext()) {
    LineItem item = lineItems.next();
    if (item.getLineItemId().productId == 100) {
        targetItem=item;
    }
}
openOrder.getLineitems().remove(targetItem);
session.save(openOrder);
session.getTransaction().commit();
session.close();

Now, this code looks like it should work; and in fact, if you run it, it will appear to work while not having the effect you expect. If you look, you will see that a row associated with the LineItem object remains in the database. The reason is that Hibernate needs to be explicitly told how to deal with “orphaned” objects that are removed from Collections. This is a ramification of the way in which Hibernate handles the notion of transient, persistent, and detached objects—as we discussed in the introduction to the “Programming Model” section. This life cycle is so important to understanding the delete operation that we repeat some of the essential details here.

When you remove the object from the collection, you’ve not changed its persistent nature. In Hibernate an object is first instantiated as transient—right after you’ve created it with the new operator, but before you’ve associated it with a Session, it is transient. After you make an object persistent—either by directly making it persistent by using Session.save() or by attaching it to a persistent object—it remains persistent. Its state remains in the database until it is removed from the database. In this case, you’ve just removed one pointer to the object—it could be that other references to the row remain in the database; Hibernate can’t know that.

What you can do, however, is to clarify the semantics of the collection. To understand that, examine the snippet from the Order.hbm.xml configuration file shown in Listing 7.18.

Example 7.18. Cascade Attribute in the Mapping File

<LINELENGTH>90</LINELENGTH>
<set name="lineitems" inverse="true" cascade="all, delete-orphan">
  <key column="ORDER_ID" not-null="true"/>
  <one-to-many class="org.pwte.example.domain.LineItem"/>
</set>

Notice the attribute cascade="all, delete-orphan". When we have said that attaching a transient object to a persistent object makes the transient object persistent, we’ve been telling a little white lie. In fact, by default no persistence operations are applied to the objects at the other end of a relationship unless you set the cascade attribute to all. Then all the persistence operations you can express through a Session (save, update, delete, and so on) are carried through (cascaded) to the other end of the relationship. The exception to this is delete-orphan. This special cascade style is what enables deleting objects when they are removed from a collection. You can either add it as we’ve shown previously, or use the combination “all-delete-orphan” style in the cascade attribute. In fact, we’ve killed two birds with one stone in setting this to all and delete-orphan, because now when we delete an Order we find that all the LineItems in the Order are also deleted, as the example in Listing 7.19 indicates.

Example 7.19. Cascaded Delete of Order and LineItems

<LINELENGTH>90</LINELENGTH>
Session session=HibernateUtil.getSessionFactory().openSession();
session.beginTransaction();
Order order = (Order) session.load(Order.class, new Integer(400));
session.delete(order);
session.getTransaction().commit();
session.close();

Much like in the update scenario, you can use HQL to issue a bulk delete.

Stored Procedures

As noted earlier, in the same way you can specify which SQL to execute for a query, you can specify custom SQL for update, insert, and delete as well. In implementing these processes, you can also specify a stored procedure for the same purpose. So if you wanted to build a hypothetical class “Address” whose SQL would be controlled through stored procedures, its mapping file might look as shown in Listing 7.20.

Example 7.20. Specifying Stored Procedures in Hibernate

<LINELENGTH>90</LINELENGTH>
<class name="org.pwte.domain.Address">
    <id name="address_id"/>
    <property name="name" not-null="true"/>
    <property name="address" not-null="true"/>
    <property name="city" not-null="true"/>
    <property name="state" not-null="true"/>
    <property name="zip" not-null="true"/>
    <sql-insert callable="true">
      {call createAddress (?, ?, ?, ?, ?, ?)}
    </sql-insert>
    <sql-delete callable="true">
      {? = call deleteAddress (?)}
    </sql-delete>
    <sql-update callable="true">
     {? = call updateAddress (?, ?, ?, ?, ?, ?)}
    </sql-update>
</class>

Using this approach, you can still use Hibernate as your object-relational mapping tool even if your database administrators want to maintain strict control over the SQL that executes on the database. Now, Hibernate does put some restrictions on the stored procedures themselves, such as the fact that they have to use the standard SQL92 call form, and not a vendor-specific syntax. These limitations and restrictions are described in the Hibernate documentation. Finally, to specify a SQL query or stored procedure for loading a class (for example, for identity queries using the load() method of Session), a two-step procedure is followed. First you declare a named SQL query with the <sql-query> tag as described previously, and then you specify that it is to be used as the loading query for the class with the <loader> tag. Note that this approach lets you define only stored procedures that correspond to inserts, deletes, updates, and queries; there is no predefined way in Hibernate to use stored procedures to implement generic business logic.

Batch Operations

Hibernate allows you to batch updates and inserts into the database; to do so effectively you must set a configuration parameter, and also adjust the application code that triggers the batched updates or inserts. First, you want to make sure that you enable batching by setting the JDBC batch size to a reasonable value in the Hibernate main configuration file (Hibernate.cfg.xml):

<LINELENGTH>90</LINELENGTH>
hibernate.jdbc.batch_size 50

Likewise, you need to make sure that if you’re creating or updating a very large number of objects, you occasionally call the flush() and clear() methods of Session to flush the first-level cache to the database and thus control its size. When you’re doing a significant amount of batch processing, it would also probably be useful to entirely disable the second-level cache by setting the CacheMode appropriately because you would not need to store query results when performing batch updates.

In addition, as we mentioned in the “Create,” “Update,” and “Delete” sections, you can use HQL to run a batch insert, update, or delete.

Extending the Framework

Hibernate has a range of extension points, including the UserType extension point discussed later in “Attributes” and the Cache extension point described in the later “Caching” section.

Error Handling

One of the things that is difficult for people who have had experience only with JDBC to become adjusted to in the Hibernate programming model is the lack of checked exceptions. Hibernate API methods do not (in general) throw checked exceptions—they are all instead declared to throw HibernateException, which is a RuntimeException. This is a practice that is also present in Spring, and that is preferred by many developers. Thus, in many cases you do not need to encapsulate your code in try-catch blocks as you would in JDBC. There are cases in which you would want to catch specific subclasses of HibernateException, though, and we will cover that topic later with detailed examples.

ORM Features Supported

Hibernate supports most standard features required for object-relational mapping. It transparently handles object mapping, key generation, inheritance, relationships, and derived mappings. As we have seen, it also provides flexibility to the user in handling odd cases such as where stored procedures are required, or where special SQL needs to be specified for custom queries. With its strong support for third-party caching facilities, it makes it easier to achieve good runtime performance by alleviating the need to constantly reexecute queries against the database. In short, it tries to cover all the bases in the game of ORM.

Objects

Defining an Order object in Hibernate is no different from defining an Order object in our standard POJO domain model. In Listing 7.21 we show the code of the Order object; as you can see, it does not differ from that presented earlier when the domain model was presented.

Example 7.21. Defining an Order Object in Hibernate

<LINELENGTH>90</LINELENGTH>
package org.pwte.example.domain;

import java.math.BigDecimal;
import java.util.Set;

public class Order {
    protected int orderId;
    protected BigDecimal total;
    public static enum Status { OPEN, SUBMITTED, CLOSED }
    protected Status status;
    protected AbstractCustomer customer;
    protected Set<LineItem> lineitems;

    //getters and setters
}

The key place where development in Hibernate begins to differ from straight POJO development is in the development of the mapping configuration files for each POJO object. Listing 7.22 shows the mapping file for our Order object.

Example 7.22. Hibernate Mapping File for Order

<LINELENGTH>90</LINELENGTH>
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
    <class name="org.pwte.example.domain.Order" table="ORDERS"
     schema="APP">
        <comment></comment>
        <id name="orderId" type="int">
            <column name="ORDER_ID" />
            <generator class="increment" />
        </id>
        <many-to-one name="customer"
               class="org.pwte.example.domain.AbstractCustomer"
               column="CUSTOMER_ID">
        </many-to-one>
        <property name="status"
         type="org.pwte.example.domain.StatusUserType">
            <column name="STATUS" length="9" not-null="true">
                <comment></comment>
            </column>
        </property>
        <property name="total" type="big_decimal">
            <column name="TOTAL" precision="14" not-null="true">
                <comment></comment>
            </column>
        </property>
        <set name="lineitems" inverse="true" cascade="all" outer-
         join="true">
            <key column="ORDER_ID" not-null="true"/>
            <one-to-many class="org.pwte.example.domain.LineItem"/>
        </set>
    </class>
</hibernate-mapping>

There are several features of this mapping file that are worthwhile to point out. The first is the class element inside the hibernate-mapping element. Note that it specifies the basic parts of the mapping between class and table, the fully qualified name of the class, the name of the relational table that it maps to, and the schema that the table is found in.

There are several types of mapping elements contained within the mapping file: they are relationships, collections, and properties. A property represents a single attribute of the class, and shows its map to a corresponding database column. A collection represents an attribute of the class that is represented by a collection; in this case, we see the element declaring that the LineItem class is a member of a Set called lineitems. Finally, a relationship describes a relationship between this class and another class, and indicates the cardinality by the name of the element (in this case many-to-one). We will cover each of these features in more depth in the following sections.

Inheritance

Inheritance

A.7.1

Hibernate supports a full set of inheritance mapping options. It supports single table, class table, and concrete table (called “table per class hierarchy,” “table per subclass,” and “table per concrete class,” respectively, in Hibernate terminology) inheritance—as described in Chapter 3. These options are supported in the Hibernate mapping files by the <subclass>, <joined-subclass>, and <union-subclass> tags, respectively. The structure of the mapping file in this case is a bit different, in that the subclass tags above appear as an element of the <class> tag of the superclass. Property, relationship, and set tags then nest within the subclass tag as usual.

Our schema and code example uses the single-table inheritance model. In Listing 7.23 the section of the Hibernate mapping file for AbstractCustomer shows how the <subclass> tag is used to define that the ResidentialCustomer class inherits from AbstractCustomer and that both use the single table inheritance model. For clarity in this example, we have left out the mapping of BusinessCustomer.

Example 7.23. Showing Inheritance in Hibernate

<LINELENGTH>90</LINELENGTH>
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
    <class name="org.pwte.example.domain.AbstractCustomer"
     table="CUSTOMER" schema="APP">
        <comment></comment>
        <id name="customerId" type="int">
            <column name="CUSTOMER_ID" />
            <generator class="assigned" />
        </id>
        <discriminator column="type" />
        <many-to-one name="openOrder"
         class="org.pwte.example.domain.Order"
         column="OPEN_ORDER"
         insert="false" update="true">
        </many-to-one>
        <set name="orders" inverse="true" outer-join="true">
            <key column="customer_id" not-null="true"/>
            <one-to-many class="org.pwte.example.domain.Order"/>
        </set>
        <property name="name" type="string">
            <column name="NAME" length="30" not-null="true">
                <comment></comment>
            </column>
        </property>
     <subclass
      name="org.pwte.example.domain.ResidentialCustomer"
      discriminator-value="RESIDENTIAL"
      extends="org.pwte.example.domain.AbstractCustomer">
        <property name="householdSize" type="java.lang.Short">
            <column name="RESIDENTIAL_HOUSEHOLD_SIZE">
                <comment></comment>
            </column>
        </property>
        <property name="frequentCustomer" type="yes_no">
            <column name="RESIDENTIAL_FREQUENT_CUSTOMER" length="1">
                <comment></comment>
            </column>
        </property>
        </subclass>
    </class>
</hibernate-mapping>

Notice in this mapping file that you first begin by specifying the superclass (AbstractCustomer) and by mapping the properties that are shared by all subclasses (in this case id, openOrder, orders, and name). An additional feature that you should pay attention to is the <discriminator column="type"/> element. This declares that a discriminator column is used to determine which subclass rows belong to. In the <subclass> element you will see the discriminator-value attribute, which declares for the ResidentialCustomer subclass that the value RESIDENTIAL will be used for the value of the discriminator column.

Keys

Hibernate allows you to map the type of the database primary key to a Java type just as with any other attribute. A restriction in the way Hibernate treats key attributes is that once a key attribute has been first assigned, Hibernate does not allow it to be changed—because in that case you would be changing the identity of the object. If you attempt to do so, Hibernate will throw a HibernateException when you attempt to commit the transaction. Hibernate supports both composite (compound) and noncomposite primary keys. A simple example of a noncomposite primary key mapping is in the Order class, as we have already seen, and as shown in Listing 7.24.

Example 7.24. Simple Integer Mapping of a Primary Key

<LINELENGTH>90</LINELENGTH>
<id name="orderId" type="int">
  <column name="ORDER_ID" />
  <generator class="increment" />
</id>

In this example, you see that the name of the attribute mapped to the database primary key column ORDER_ID is orderId and that the Java type that the SQL INTEGER type is to be mapped to is a Java int. This example also specifies a generator class, which we will examine shortly.

Composite primary keys should be used when an existing database schema requires the use of a compound key. In most other cases, you should prefer a simple (noncomposite) primary key, because it will generally make the mapping simpler. Hibernate fully supports compound keys. However, to understand the best way to use composite keys, we have to think a while about how key attributes work in Hibernate.

Let’s consider the mapping for the LineItem class. The LINE_ITEM table has a composite primary key consisting of both ORDER_ID and PRODUCT_ID. However, the LineItem class definition does not even contain an orderId attribute; and instead of a productId, it contains only a reference to an actual product! So, one way of handling this might be to add a productId attribute to the class. However, remember how objects are loaded in Hibernate; the Session.load() method takes two parameters—the class of the object being loaded, and the primary key value (which may be an Object). There’s no easy way to see how both primary key values would be specified. In fact, Hibernate allows a way out of this by permitting you to use a “dummy” instance of the class being loaded as the primary key, but this is not a very elegant solution to our problem. A better solution is to introduce a new class to serve as the primary key of the LineItem class. This class, called LineItemId, is shown in Listing 7.25.

Example 7.25. Compound Key Classes in Hibernate

<LINELENGTH>90</LINELENGTH>
package org.pwte.example.domain;

import java.io.Serializable;

public class LineItemId implements Serializable {

    private static final long serialVersionUID = 4790624621686438011L;
    public int productId;
    public int orderId;

    public LineItemId() {
        super();
    }

    //getters and setters

    public boolean equals(Object anObject) {
        // Implement equality logic
    }
}

To use this class, we would then have to modify the LineItem class to include an attribute of this type, as is shown in Listing 7.26.

Example 7.26. LineItem Modified to Use LineItemId

<LINELENGTH>90</LINELENGTH>
public class LineItem implements Serializable {

    protected long quantity;
    protected BigDecimal amount;
    protected Product product;
    protected LineItemId lineItemId;

    public LineItem() {
        super();
    }

    public LineItem(long quantity, BigDecimal amount, Product product,
        LineItemId lineItemId) {
        super();
        this.quantity = quantity;
        this.amount = amount;
        this.product = product;
        this.lineItemId = lineItemId;
    }

    public LineItemId getLineItemId() {
        return lineItemId;
    }

    public void setLineItemId(LineItemId lineItemId) {
        this.lineItemId = lineItemId;
    }

    /** Other getter and setter methods omitted for clarity **/
}

Now that we’ve modified the LineItem class to contain a LineItemId, we can show the section of the LineItem mapping file that would allow us to take advantage of this new type, as shown in Listing 7.27.

Example 7.27. Using LineItemId in Hibernate

<LINELENGTH>90</LINELENGTH>
<composite-id name="lineItemId" class="org.pwte.example.domain.LineItemId">
        <key-property name="productId" column="product_id" type="int"/>
        <key-property name="orderId" column="order_id" type="int"/>
</composite-id>

In this example you see that the key columns of the LINE_ITEM table map to the productId and orderId properties of the LineItemId class. This would allow you to use instances of this type as the primary key value in Hibernate.

Now that we’ve discussed composite keys in Hibernate, we can return to our discussion of the Order primary key. We noted earlier that the <id> element contained a <generator class="increment" /> element but did not explain it. This element is used to specify the key generation approach that Hibernate should take for autogenerated primary keys.

Hibernate supports both key generation by the database and its own key generation schemes. Table 7.2 summarizes the major key generation schemes supported by Hibernate.

Table 7.2. Key Generation Schemes in Hibernate

Generator Attribute Name

Description

Identity

This generator supports identity columns in the database. This option is supported only for certain databases. At the time of writing, this included DB2, MySQL, MS SQL Server, Sybase, and HypersonicSQL.

Increment

Hibernate will read the maximum primary key value from the table at startup and then increment an in-memory value by one for each new object created. Should be used only when a single-server instance of Hibernate has exclusive access to the database; good for testing, but not recommended for production.

Hilo

Uses a hi-lo algorithm to create a primary key from a retrieved high value and a local low value. This is an approach that can avoid congestion when accessing a single source for inserts. For more information, see the Hibernate documentation [Hibernate].

Guid

Uses a globally unique identifier provided by the database. Available only on MySQL and SQL Server.

sequence

Creates a sequence in the database. Supported only for DB2, PostgreSQL, Oracle, SAP DB, McKol, and Interbase

seqhilo

Uses the hi-lo algorithm with a provided database sequence to generate the high value.

uuid.hex

Generates a 128-bit global UUID.

select

Retrieves a primary key generated by a database trigger; i.e., the primary key is generated by the database—Hibernate just reads it.

native

Picks the best identity generator, depending on the capabilities of the underlying database

There are situations in which you would want to use each of these, but detailing all the choices that go into that decision is beyond the scope of this book. The choice we’ve used for our example (increment), for instance, is a good choice only if you are running Hibernate with a single server accessing the database; because the counter is held in memory, you could potentially get conflicts in a clustered environment. In many cases, either hilo or seqhilo are good choices because they minimize the number of database round-trips. When using these approaches, database round-trips are needed only if a new range of numbers must be requested for the process.

Attributes

The simplest level of mapping in Hibernate is to map individual column values to Java primitives or objects. This is done (as shown in earlier examples) through the <property> tag in the Hibernate mapping file for each class. Hibernate supports mapping attributes of the standard Java primitive types, as well as other Java object types such as Date; String; the Number classes, such as Integer, BigInteger, and BigDecimal; and java.sql types like java.sql.Clob.

A simple example of this can be seen in Listing 7.28, in the mapping of the quantity attribute from LineItem, which maps to a BIGINT type in Derby.

Example 7.28. Quantity Attribute Mapping

<LINELENGTH>90</LINELENGTH>
<property name="quantity" type="long">
  <column name="quantity" not-null="true" />
</property>

An interesting case of attribute mapping to consider is how Hibernate can handle mapping the Order.Status enumeration to the STATUS column (of type varchar(9)) in our Derby schema. To understand how that mapping (shown in Listing 7.29) operates, we will have to take a brief look at the notion of user types in Hibernate.

Example 7.29. Status Mapping in Hibernate

<LINELENGTH>90</LINELENGTH>
<property name="status" type="org.pwte.example.domain.StatusUserType">
  <column name="STATUS" length="9" not-null="true"/>
</property>

Here we see that this looks like a standard Hibernate property element, but the difference comes in on the type—in this type it maps not to a standard Java type but instead to a class we have defined called org.pwte.example.domain.StatusUserType.

The issue here is that Hibernate has no standard “out of the box” mapping for Java 5 Enumerations. However, the Hibernate documentation provides an example that can be adapted easily to solve the problem at hand. The way in which this example solves this is by creating a new type that is a subclass of org.hibernate.usertype.UserType. UserType is a standard Hibernate extension point that allows a developer to write his own code to specify how to map from any arbitrary Java representation to a database column. It does this through implementing two methods (among a host of others that each UserType subclass must implement). These two key methods are nullSafeGet and nullSafeSet. The method nullSafeGet reads the value of the column from a ResultSet and then returns the Object that represents the property. Correspondingly, the method nullSafeSet takes in the object representing that property and then writes an equivalent value to a JDBC PreparedStatement.

There are many other methods of UserType that should also be overridden in a UserType subclass; for more information, we refer the interested reader to the Hibernate documentation, or to the code example provided.

Contained Objects

Hibernate supports the notion of contained objects, which it refers to as Components. In this way, a value type, such as an Address, could be contained within a Customer while both are stored in the same table. To show this example, we will move away from our domain classes, and set up a theoretical “Customer” class that would reference an “Address” class. The class definition of Customer would look as shown in Listing 7.30.

Example 7.30. Customer Containing Address

<LINELENGTH>90</LINELENGTH>
public class Customer {
    private int id;
    private String name;
    private Address address;
    public Address getAddress() {
        return address;
    }
    public void setAddress(Address address) {
        this.address = address;
    }
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    ...

}

The definition of Address would be as shown in Listing 7.31.

Example 7.31. Address Class

<LINELENGTH>90</LINELENGTH>
public class Address {
    private String streetAddress;
    private String city;
    private String state;
    private String zip;

//getters and setters

}

You map a Hibernate component into its containing class using the <component> element in the Hibernate mapping file, as is shown in Listing 7.32.

Example 7.32. Customer and Address Mapping File

<LINELENGTH>90</LINELENGTH>
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
  "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
  "hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
   <class name="org.pwte.example.domain.hibernate.Customer"
    table="ADDRESSED_CUSTOMER" schema="APP">
      <id name="id" type="int">
          <column name="CUSTOMER_ID" />
          <generator class="assigned" />
      </id>
      <property name="name" type="string">
          <column name="NAME" length="30" not-null="true">
          </column>
      </property>
      <component name="address"
       class="org.pwte.example.domain.hibernate.Address" >
        <property name="streetAddress" type = "string"
         column="STREET_ADDRESS" not-null="true"/>
        <property name="city" type="string"
         column="CITY"not-null="true"/>
        <property name="state" type="string"
         column="STATE" not-null="true"/>
        <property name="zip" type="string"
         column="ZIP" not-null="true"/>
      </component>
   </class>
</hibernate-mapping>

In this case, all the properties of both the Customer class and the Address class are mapped to columns from the ADDRESSED_CUSTOMER table.

Relationships

Relationships are represented in the Hibernate Core POJO model by simple object relationships. A one-to-one relationship or many-to-one relationship is represented by having an object contain an instance variable that holds another object, while a one-to-N relationship is represented through a Java collection type or an array. These object-level relationships are mapped to relations between tables in the Hibernate mapping files.

For instance, to show how an Order is owned by one and only one customer, you declare that there is a many-to-one relationship between Order and AbstractCustomer (for example, many Orders belong to one customer). This is performed in the Hibernate mapping file for Order by the element in the configuration shown in Listing 7.33.

Example 7.33. Many-to-One Mapping Element for Order to Customer

<LINELENGTH>90</LINELENGTH>
<many-to-one name="customer"
    class="org.pwte.example.domain.AbstractCustomer"
    column="CUSTOMER_ID">
</many-to-one>

On the other hand, each customer can own many orders (not only their open order, but all other orders they have placed or abandoned as well). This is done not in the Order mapping file, but in the AbstractCustomer mapping file, with the configuration elements shown in Listing 7.34.

Example 7.34. Set Mapping Element for Orders in AbstractCustomer Mapping File

<LINELENGTH>90</LINELENGTH>
<set name="orders" inverse="true" outer-join="true">
    <key column="customer_id" not-null="true"/>
    <one-to-many class="org.pwte.example.domain.Order"/>
</set>

Here we see that the AbstractCustomer has many orders (of type Order from the common example package), and that they are represented as a Set. The inverse attribute relates the fact that this relationship has a mirror image; that each Order also has a corresponding AbstractCustomer. In Listing 7.35, we show how to reference the singleton open order in the AbstractCustomer mapping.

Example 7.35. Open Order Mapping in AbstractCustomer Mapping File

<LINELENGTH>90</LINELENGTH>
<many-to-one name="openOrder"
    class="org.pwte.example.domain.Order"
    column="OPEN_ORDER" />

Finally, as an added example, let’s consider the unidirectional, many-to-one relationship between Product and LineItem we saw the Java code for earlier. In this case, the way that this is mapped in the LineItem mapping file is with the element shown in Listing 7.36.

Example 7.36. Product Mapping for LineItem

<LINELENGTH>90</LINELENGTH>
<many-to-one name="product"
   class="org.pwte.example.domain.Product"
   column="product_id"
   insert="false" update="false">
</many-to-one>

We see that the attribute product in LineItem will contain an instance of org.pwte.example.domain.Product. The way that this will be mapped is that the product_id column in the LineItem table acts as a foreign key to the table that the Product class is mapped to.

Careful readers will notice that we’ve used two additional attributes: insert="false" and update="false". This is because the product attribute maps to part of the primary key of LineItem. This will prevent a developer from trying to change the Product in the LineItem, and thus its primary key value.

Constraints

Hibernate has the capability to support many types of constraints. Hibernate supports column constraints (SQL domains) with the sql-type attribute of the <column> element in the Hibernate mapping file. More complex column constraints can be specified against some databases through the use of the check attribute of the <column> element, which allows the use of regular expressions in specifying column value constraints. Table-level constraints can be specified through the check attribute of the <class> element in the Hibernate mapping file.

Some types of general database constraints, such as foreign key constraints, are picked up automatically through the foreign key attribute of the <many-to-one> element. Finally, Hibernate allows you to specify your own general constraints using the <database-object> elements. For more information on constraint support in Hibernate, refer to the Hibernate documentation.

Derived Attributes

Hibernate offers comprehensive support for derived attributes. It supports deriving attributes from simple calculations (for example, multiplying two column values together), as well as more comprehensive support that includes deriving an attribute value from properties of the object being loaded, as well as values from other tables. The example in Listing 7.37 shows how you can use a subselect to derive a LineItem’s amount attribute from the quantity in the LineItem multiplied by the price of the Lineitem’s product.

Example 7.37. Setting the amount Attribute in a Line Item

<LINELENGTH>90</LINELENGTH>
<property name="amount" type="big_decimal"
   formula="(
       select (QUANTITY * p.PRICE) from Product p
       where p.PRODUCT_ID = PRODUCT_ID)" />

In addition to simple formulas and subselects, Hibernate also supports using SQL functions like avg() and sum() in its formulas. The example in Listing 7.38 shows how you could use this support to define that the Order’s total property is computed by summing its associated line item amounts.

Example 7.38. Setting the total Attribute in Order

<LINELENGTH>90</LINELENGTH>
<property name="total" type="big_decimal"
    formula="(
        select SUM(l.AMOUNT) from LINE_ITEM l
        where l.ORDER_ID = ORDER_ID)" />

A note to keep in mind about attributes that are derived is that the derived value is originally retrieved from the database when the object is first loaded. You can change the value of the attribute in the object but that will not change the value on the database. Calculating a total is a good example of when this would be applicable; you couldn’t know how to update a total value if the value is calculated from a sum of line item amounts. Likewise, if the values in the database change after the object is fetched, it will not be automatically updated with a new calculated value.

Tuning Options

Hibernate is highly optimized for performance, while not sacrificing its simple programming model, and supports a number of different tuning options, as the following sections describe.

Query Optimizations

Hibernate provides a range of options for query optimization, many of which involve reducing either the number of rows returned through batching queries, or the number of queries through joins. The basic range of options on how this is done is discussed in the “Loading Related Objects” section. You can also use specifically tuned SQL statements, including proprietary extensions of the database at hand.

Caching

At one point caching support was a drawback of earlier versions of Hibernate, but the 3.0 and later versions contain very sophisticated support for caches of different scopes. The first level of caching is in the Hibernate Session. Cache entries have a transaction scope by default, but also can be scoped to the lifetime of Session. Actual persistent objects that have been fetched are stored in the first-level cache, which is used not so much for performance reasons, but to guarantee proper object identity within a Session and maintain objects for a particular life cycle scope, such as a transaction. The second-level cache is at the Process Scope/Cluster Scope (which is actually at the SessionFactory Scope). It does not store instances, but instead stores only the state of instances in an internal form to minimize the memory requirements—this cache is the one whose major purpose is to improve performance. Finally, there is a query results cache that is related to the second-level cache. Hibernate enables you to specify how this second-level cache will work by allowing the specification of a cache policy that describes the concurrency strategy, cache expiration policies, and the physical format of the cache.

Hibernate supports several external cache providers that host and implement the second-level cache, including these:

  • EHCache—Simple support for single-JVM and distributed caching features.

  • OpenSymphony OSCache—Cache to memory and disk with nice expiration options.

  • SwarmCache—A cluster cache based on JGroups.

  • JBoss TreeCache—A fully transactional replicated cache.

  • Custom—Developers can choose to implement org.hibernate.cache.Cache Provider; this is the integration point for many commercial third-party cache products.

Loading Related Objects

When looking at how related objects are loaded, it is a good idea to keep in mind two different questions: when the objects are loaded, and how they are loaded. Hibernate provides facilities to help specify how both questions should be answered. In the first instance, Hibernate includes a way of specifying different loading modes (lazy or eager) for queries. These are the supported mappings modes:

  • Lazy fetching—Associated Object is fetched lazily through a proxy (the default).

  • Eager fetching—Associated Object is fetched immediately with a separate SQL statement.

When tuning a persistent object, sometimes it is most efficient to use a lazy loading technique that defers the loading of a relationship until the time the collection is first referenced. To declare a collection as lazy, you can mark the attribute as being lazy-loaded in the hibernate mapping file for the class, as shown in Listing 7.39.

Example 7.39. Lazy Loading in Hibernate

<LINELENGTH>90</LINELENGTH>
<set name="lineItems" inverse="true" lazy="true">
    <key>
        <column name="order_id"/>
    </key>
    <one-to-many class="org.pwte.example.domain.LineItem"/>
</set>

It should be stated that for collections, lazy loading is the default state; so doing nothing would in this case have the same effect. On the opposite end of the spectrum from lazy loading is eager loading, or retrieving the contents of a relationship in a single SQL statement. How to implement eager loading depends on the type of relationship you want to eagerly load. Consider the following case from LineItem.hbm.xml, shown in Listing 7.40.

Example 7.40. Eager Loading in Hibernate

<LINELENGTH>90</LINELENGTH>
<many-to-one name="product"
    class="org.pwte.example.domain.Product"
    column="product_id"
    lazy="false"
    insert="false" update="false">
</many-to-one>

This relationship differs from others you have seen in the addition of the lazy attribute to the many-to-one element. The default value of the lazy attribute is proxy, which means that a proxy is used in place of the Product class until the Product is actually used (when it is accessed). When lazy is changed to false, the Product instance will be eagerly loaded.

If setting the lazy attribute addresses the question of when the relationship is traversed and the object loaded, then the question of how the object or objects are to be loaded is the next issue. Here Hibernate gives us the following options:

  • Join Fetching—Retrieve the associated object (or collection) in the same select as the outer object using a SQL outer join.

  • Select Fetching—Use a separate SQL SELECT to retrieve the referenced object or collection. This is the default, and unless you specifically set the lazy attribute to false, this SELECT will be executed only when you access the object or collection.

  • Subselect Fetching—Use a SQL subselect clause to retrieve the referenced object or collection in the same SELECT statement as the outer object.

  • Batch Fetching—A way of improving performance of lazy loading by fetching a set of a specified size with a given set of primary keys; this is especially helpful for very large collections so that not all elements are loaded into memory at once.

Any of these options can be used with lazy or eager loading; the mechanism for choosing them differs. For instance, in conjunction with this last option is the batch-size attribute that can be used to tune the number of selects that it takes to load a set of objects into memory. When you apply this attribute to a collection, Hibernate will (effectively) group a set of keys together and then execute a single query that brings back multiple rows. The configuration for this is shown in Listing 7.41.

Example 7.41. Batch Size Setting for Collections

<LINELENGTH>90</LINELENGTH>
<set name="lineitems" inverse="true"
 cascade="all, delete-orphan" batch-size="10">
   <key column="ORDER_ID" not-null="true"/>
   <one-to-many class="org.pwte.example.domain.LineItem"/>
</set>

Join fetching for sets can be seen if we make a small change to the mapping for LineItems from Order.hbm.xml, as shown in Listing 7.42.

Example 7.42. Outer Join in Hibernate

<LINELENGTH>90</LINELENGTH>
<set name="lineItems" inverse="true" fetch="join">
   <key>
      <column name="order_id"/>
   </key>
   <one-to-many class=
    "org.pwte.example.domain.LineItem"/>
</set>

What this does is to enable the use of an outer join to combine the retrieval of the containing object (the Order) together with the retrieval of the contained objects (in this case, the LineItems). With one simple attribute change, you can eliminate the “N + 1 SQL statement problem” that has plagued object-relational mapping solutions when dealing with contained collections for many years. This problem occurs when you select a collection of objects that each contain a referenced object; for instance, when a Customer contains an address. In this problem, when you run the single select to obtain a list of “N” Customers, you would find that you would have to execute “N” SELECT statements to retrieve all the Addresses that correspond to those Customers. By performing an outer join, we can (as we see in this example) retrieve not only our Order but the referenced LineItems as well. Of course, this opens up the possibility that you may be retrieving too much data—so this is an approach you should consider carefully.

Locking

In persistence programming there are two general notions of how you can provide concurrency on the database: either locking an object on the database (called pessimistic concurrency), or using a versioning scheme that allows you to avoid database locks altogether (called optimistic concurrency). Hibernate provides full support for both concurrency models. We’ll consider pessimistic locking first.

Your first option for specifying pessimistic concurrency is to globally set the isolation level for the connections your application is using. As is described in the earlier section “Initialization,” you can set the isolation level of a Hibernate-managed connection in the Hibernate configuration file; if a connection is obtained from a DataSource, the isolation level must be configured externally. The issue with simply setting an isolation level on a database connection is that it is a global setting, and may be too restrictive if you only need to specify the behavior of a specific transaction. For that purpose, Hibernate supports the use of database pessimistic concurrency features like select...for update. Because these features vary from database dialect to database dialect, Hibernate supports them across dialects by introducing the Session.lock() method.

Session.lock() takes two parameters, the first being a Hibernate LockMode constant and the second being the object upon which the lock should be obtained. There are several different constants defined in LockMode, but the most common of these for our purposes are LockMode.UPGRADE and LockMode.UPGRADE_NOWAIT. The LockMode.UPGRADE constant specifies that for the specified object Hibernate should perform a version check, and try to obtain a pessimistic upgrade lock using select...for update if available. LockMode.UPGRADE_ NOWAIT specifies that it should use a select...for update nowait instead, which will throw an exception if a lock cannot be obtained immediately.

Up to this point we have considered querying and modifying objects in Hibernate only within the context of a single Hibernate transaction or JTA transaction. However, in the Hibernate model, a persistent object can also act as a Data Transfer Object (DTO), which means that it can be returned from a services method, perhaps displayed in a UI, and then used again as a parameter to another services method. In this case, Hibernate objects can implement the version number pattern so that the same hibernate object used as a DTO can be reassociated with a different Hibernate Session, and it will have optimistic locking semantics. Objects may move from a transient state (not tied to any persistence framework) to a persistent state and back again based on whether they are tied to the Hibernate Session. This is useful, for example, to send an object to a client for modification and then update the database based on the changed object.

Optimistic cases are handled quite differently from pessimistic cases. To handle optimistic locking cases, when persistent objects become transient again, this object is considered a disconnected object. This object can then be reconnected to another Session, and Hibernate can use the Version Number pattern to compare it against the actual data store and check whether the object is still in a valid state. In some situations, a particular layer of your code can be interacting with an object and not know what state it is in. If a persistent object is passed to another method, this method must know its intended behavioral aspects to make sure things are handled correctly.

The way in which Hibernate supports optimistic locking using versioning (or timestamping, which is similar) is that you need to provide a version (or timestamp) attribute in your Java class that will match to a corresponding field in your database schema. So if our Product class was to be maintained with optimistic concurrency, we could add a new field versionNumber to the definition to correspond to a new database column named VERSION as Listing 7.43 shows.

Example 7.43. Product Definition with Version Number

<LINELENGTH>90</LINELENGTH>
public class Product implements Serializable {
...
  protected int productId;
    private int versionNumber;
... }

Then you would modify the Product.hbm.xml configuration file to add the following line after the <id> mapping element:

<LINELENGTH>90</LINELENGTH>
<version name="versionNumber" access="field" column="VERSION"/>

Development Process for the Common Example

One of the more helpful parts of Hibernate to a developer is that its flexibility allows you to build and test your persistence code however you would like; either you can use an IDE like Eclipse or Rational Software Architect and test your code in an embedded JEE container, or you can simply develop with Notepad and the Java compiler and build and test your code at the command line with Ant, Maven, and JUnit. Thus, the typical development process for Hibernate would be whatever you are comfortable, and familiar with.

Defining the Objects

Because Hibernate uses a POJO-based model, defining objects for persistence with Hibernate is as simple as developing any set of objects that follow the JavaBean naming conventions. The only cases in which you would want to explicitly define an object that is specific to a Hibernate model as opposed to a generic domain model might be when you need to declare a separate ID type to represent a composite primary key, such as we have had to do with the LineItemId class shown previously. In all other respects, the Java code for the Hibernate persistent classes for the common example are the same as the standard POJO versions of those classes described in Chapter 3, “Designing Persistent Object Services,” and shown in Chapter 6, “Apache iBATIS.”

As we have also seen already, the other primary action a Hibernate developer has to take is to build a mapping file for each of his persistent objects. Although this can be done entirely by hand in Notepad or in an XML editor, Hibernate also provides Eclipse plug-ins that can help automate this process and make it easier.

In the next few listings we will show the example code for the end-to-end example. First, in Listing 7.44, we show the code for AbstractCustomer.

Example 7.44. AbstractCustomer

<LINELENGTH>90</LINELENGTH>
package org.pwte.example.domain;

import java.io.Serializable;
import java.util.Set;

public abstract class AbstractCustomer implements Serializable {
    protected int customerId;
    protected String name;
    protected Order openOrder;
    protected Set<Order> orders;

    //getters and setters

}

Next, in Listing 7.45, we see the code for BusinessCustomer.

Example 7.45. BusinessCustomer

<LINELENGTH>90</LINELENGTH>
package org.pwte.example.domain;

import java.io.Serializable;
import java.util.Set;

public class BusinessCustomer extends AbstractCustomer implements Serializable {
    private static final long serialVersionUID = -1210978439928403907L;
    protected boolean volumeDiscount, businessPartner;
    protected String description;

    getters and setters
}

Then we see the code for ResidentialCustomer, in Listing 7.46.

Example 7.46. ResidentialCustomer

<LINELENGTH>90</LINELENGTH>
package org.pwte.example.domain;

import java.io.Serializable;
import java.util.Set;

public class ResidentialCustomer
extends AbstractCustomer implements Serializable {
    protected short householdSize;
    protected boolean frequentCustomer;

    //getters and setters

All three of these classes (which, as described earlier, are related by inheritance, and for which we have chosen the single-table mapping pattern) are described by the AbstractCustomer.hbm.xml mapping file, as shown in Listing 7.47.

Example 7.47. AbstractCustomer.hbm.xml

<LINELENGTH>90</LINELENGTH>
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
  "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
  "hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<!-- Generated May 20, 2007 5:19:52 PM by Hibernate Tools 3.2.0.b9 -->
<hibernate-mapping>
    <class name="org.pwte.example.domain.AbstractCustomer"
     table="CUSTOMER" schema="APP" lazy="false">
        <comment></comment>
        <id name="customerId" type="int">
            <column name="CUSTOMER_ID" />
            <generator class="assigned" />
        </id>
        <discriminator column="type" />
        <many-to-one name="openOrder"
         class="org.pwte.example.domain.Order"
         column="OPEN_ORDER"
         lazy="false">
        </many-to-one>
        <set name="orders" inverse="true"
         outer-join="true" lazy="false">
            <key column="customer_id" not-null="true"/>
            <one-to-many class="org.pwte.example.domain.Order"/>
        </set>
        <property name="name" type="string">
            <column name="NAME" length="30" not-null="true">
                <comment></comment>
            </column>
        </property>
        <subclass
         name="org.pwte.example.domain.BusinessCustomer"
         discriminator-value="BUSINESS"
         extends="org.pwte.example.domain.AbstractCustomer"
         lazy="false">
            <property name="volumeDiscount" type="yes_no">
              <column name="BUSINESS_VOLUME_DISCOUNT" length="1">
                <comment></comment>
              </column>
            </property>
            <property name="businessPartner" type="yes_no">
               <column name="BUSINESS_PARTNER" length="1">
                 <comment></comment>
               </column>
            </property>
            <property name="description" type="java.lang.String">
               <column name="BUSINESS_DESCRIPTION">
                 <comment></comment>
               </column>
            </property>
        </subclass>
        <subclass
         name="org.pwte.example.domain.ResidentialCustomer"
         discriminator-value="RESIDENTIAL"
         extends="org.pwte.example.domain.AbstractCustomer"
         lazy="false">
            <property name="householdSize" type="java.lang.Short">
               <column name="RESIDENTIAL_HOUSEHOLD_SIZE">
                 <comment></comment>
               </column>
            </property>
            <property name="frequentCustomer" type="yes_no">
               <column name="RESIDENTIAL_FREQUENT_CUSTOMER" length="1">
                 <comment></comment>
               </column>
            </property>

        </subclass>
    </class>
</hibernate-mapping>

The Order class was previously shown in Listing 7.21. Its mapping file was shown in Listing 7.22. Order uses the class LineItem, which is shown in Listing 7.48.

Example 7.48. LineItem

<LINELENGTH>90</LINELENGTH>
package org.pwte.example.domain;

import java.io.Serializable;
import java.math.BigDecimal;

public class LineItem implements Serializable {
    private static final long serialVersionUID = -5969434202374976648L;
    protected long quantity;
    protected BigDecimal amount;
    protected Product product;
    protected LineItemId lineItemId;

    public LineItem() {
        super();
    }

    //getters and setters

Remember that LineItem references LineItemId, which was previously shown in Listing 7.25. LineItem is described by the LineItem.hbm.xml mapping file, shown in Listing 7.49.

Example 7.49. LineItem.hbm.xml

<LINELENGTH>90</LINELENGTH>
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
  "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
  "hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<!-- Generated May 20, 2007 5:19:52 PM by Hibernate Tools 3.2.0.b9 -->
<hibernate-mapping>
    <class name="org.pwte.example.domain.LineItem"
     table="LINE_ITEM" schema="APP" lazy="false">
        <comment></comment>
        <composite-id name="lineItemId"
         class="org.pwte.example.domain.LineItemId">
           <key-property name="productId"
            column="product_id" type="int"/>
           <key-property name="orderId" column="order_id" type="int"/>
        </composite-id>
        <property name="quantity" type="long">
           <column name="quantity" not-null="true">
              <comment></comment>
           </column>
        </property>
        <property name="amount" type="big_decimal">
           <column name="AMOUNT" precision="14" not-null="true">
              <comment></comment>
           </column>
        </property>
        <many-to-one name="product"
         class="org.pwte.example.domain.Product"
         column="product_id"
         lazy="false"
         insert="false" update="false">
        </many-to-one>
    </class>
</hibernate-mapping>

Finally, we show the implementation of the Product class, in Listing 7.50.

Example 7.50. Product class

<LINELENGTH>90</LINELENGTH>
package org.pwte.example.domain;

import java.io.Serializable;
import java.math.BigDecimal;

public class Product implements Serializable {
    private static final long serialVersionUID = 2435504714077372968L;
    protected int productId;
    protected BigDecimal price;
    protected String description;

    //getters and setters

As with the other classes in the example, the Product class is described by its corresponding Product.hbm.xml mapping file, which is shown in Listing 7.51.

Example 7.51. Product.hbm.xml

<LINELENGTH>90</LINELENGTH>
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<!-- Generated May 20, 2007 5:19:52 PM by Hibernate Tools 3.2.0.b9 -->
<hibernate-mapping>
    <class name="org.pwte.example.domain.Product"
     table="PRODUCT" schema="APP" lazy="false">
        <comment></comment>
        <id name="productId" type="int">
            <column name="PRODUCT_ID" />
            <generator class="assigned" />
        </id>
        <property name="price" type="big_decimal">
            <column name="PRICE" precision="14" not-null="true">
                <comment></comment>
            </column>
        </property>
        <property name="description" type="java.lang.String">
            <column name="DESCRIPTION" not-null="true">
                <comment></comment>
            </column>
        </property>
    </class>
    <sql-query name="classifiedProducts">
    <return alias="product" class="org.pwte.example.domain.Product"/>
    SELECT product.PRODUCT_ID AS {product.productId},
           product.PRICE AS {product.price},
           product.DESCRIPTION AS {product.description}
    FROM PRODUCT product
    WHERE product.description LIKE :namePattern
    </sql-query>
</hibernate-mapping>

Implementing the Services

Implementing services in Hibernate is something that can be accelerated through the flexibility of the Hibernate design. For instance, we’ve already seen how Hibernate can work easily either with an external transaction manager like JTA or with its own transaction management API. Likewise, it can work just as easily with JTA transactions that are managed through declarative transaction management in an EJB.

The following listings show the implementation of the example service in Hibernate. There are a couple of things to keep in mind about these examples. First of all, each service begins and ends its own transaction; they are not set up to deal with external transaction management, although that could easily be done by removing the transaction management from within the methods and relying on JTA. Second, in these examples we return objects that will then be examined outside of a Hibernate Session (by our JUnit tests). For this reason, we have needed to set the lazy="false" parameter on all classes in the configuration files, for all relationships, and for all collections. This can have an adverse effect on performance and would not be recommended for normal operation with Hibernate.

bootstrapping

As shown previously, we simply use the HibernateUtil to get a Hibernate Session.

loadCustomer

Listing 7.52 shows how we implement the common load method to get the customer. This method is used by various service operation implementations. The assumption made here is that the transaction is already started by the calling routine, so it retrieves only the current Session.

Example 7.52. loadCustomer

<LINELENGTH>90</LINELENGTH>
public AbstractCustomer loadCustomer(int customerId)
throws CustomerDoesNotExistException, GeneralPersistenceException {
    try {

        // 1. Get a Hibernate Session, but assume tran started
        Session session = HibernateUtil.getSessionFactory()
                .getCurrentSession();
        // 2. Find the customer for this customerId
        AbstractCustomer customer = null;
        customer = (AbstractCustomer) session.load(
                AbstractCustomer.class, new Integer(customerId));
        return customer;
    }
    catch (ObjectNotFoundException e) {
        throw new CustomerDoesNotExistException(
            "No customer exists matching id " + customerId);
    }
    catch (HibernateException e) {
        throw new GeneralPersistenceException(
            "Unexpected Exception " + e);
    }
}

openOrder

The openOrder service implementation creates an order Java Object and persists it. It then sets the new order onto the customer instance. Listing 7.53 shows the complete example.

Example 7.53. openOrder in Hibernate

<LINELENGTH>90</LINELENGTH>
public Order openOrder(int customerId)
throws CustomerDoesNotExistException, OrderAlreadyOpenException,
       GeneralPersistenceException {
    try {
        // 1.  Get a Hibernate Session
        Session session = HibernateUtil.getSessionFactory()
                .getCurrentSession();
        session.beginTransaction();

        // 2. Use the Load customer method
        AbstractCustomer customer = loadCustomer(customerId);

        // 3. Check to see if the customer has an open order
        if (customer.getOpenOrder() != null)
            throw new OrderAlreadyOpenException(
                "Order already open for customer matching id "
                                + customerId);

        // 4. Create the new order and save it
        Order myOrder = new Order();
        myOrder.setStatus(Status.OPEN);
        myOrder.setCustomer(customer);
        myOrder.setTotal(new BigDecimal(0));
        session.save(myOrder);

        // 5. Update the open order in the customer
        customer.setOpenOrder(myOrder);

        // 6. Commit the transaction
        session.getTransaction().commit();

        return myOrder;
    }
    catch (HibernateException e) {
        throw new GeneralPersistenceException(
            "Unexpected Exception " + e);
    }

}

addLineItem

This is a simple and straightforward implementation that involves only loading a single object (the Customer) from the database and then creating another object (the Order) and adding it to the customer. As we have seen in earlier examples, we need only attach the Order to the customer to make it implicitly persistent. This implementation is so large that we are going to break it into two parts. In Listing 7.54 we set up the service operation and validate the customer and product parameters.

Example 7.54. addLineItem Part 1—Validate Parameters

<LINELENGTH>90</LINELENGTH>
public LineItem addLineItem(
    int customerId, int productId, long quantity
)
throws CustomerDoesNotExistException, OrderNotOpenException,
       ProductDoesNotExistException, GeneralPersistenceException {
    try {
        // 1.  Get a Hibernate Session
        Session session = HibernateUtil.getSessionFactory()
                .getCurrentSession();
        session.beginTransaction();
        // 2. Load the appropriate instance of the customer
        AbstractCustomer customer = loadCustomer(customerId);
        // 3. Get the Customer's open order
        Order openOrder = customer.getOpenOrder();
        if (openOrder == null)
            throw new OrderNotOpenException(
                "Order not open for customer matching id "
                                + customerId);
        // 4. Get the Product from the product Id
        Product product = null;
        try {
            product = (Product) session.load(
                Product.class, new Integer(productId));
        } catch (ObjectNotFoundException e) {
            throw new ProductDoesNotExistException(
                "No product exists matching id "+ productId);
        }
        // See Listing 7.54 for remainder of logic
    } catch (HibernateException e) {
        throw new GeneralPersistenceException(
            "Unexpected Exception " + e);
    }

Listing 7.55 continues the implementation logic of the addLineItem service operation by populating the appropriate LineItem instance, depending on whether it already exists or not. For readability, the indent is different that it should be if “unrolled” into Listing 7.54.

Example 7.55. addLineItem Part 2—Populate an Existing or New LineItem

<LINELENGTH>90</LINELENGTH>
// 5. Calculate the amount of the line item from the price and quantity
BigDecimal amount = product.getPrice().multiply(
    new BigDecimal(quantity));

// 6. Create and populate the new LineItemId
LineItemId lineItemId = new LineItemId();
lineItemId.setOrderId(openOrder.getOrderId());
lineItemId.setProductId(product.getProductId());

// 7. Check to see if the line item already exists
LineItem lineItem = null;
try {
    lineItem = (LineItem) session.load(
        LineItem.class, lineItemId);
}
catch (ObjectNotFoundException e) {
    lineItem = new LineItem();
    lineItem.setLineItemId(lineItemId);
    lineItem.setProduct(product);
}

// 8. Update the amount and quantity
lineItem.setQuantity(quantity);
lineItem.setAmount(amount);

// 9. Set the list of line items if needed
// and add the new line Item to the list
if (openOrder.getLineitems() == null) {
    openOrder.setLineitems(new HashSet<LineItem>());
}
openOrder.getLineitems().add(lineItem);
// 10. Commit the transaction
session.getTransaction().commit();

return lineItem;

removeLineItem

This service operation implementation deletes the LineItem by finding the associated record and removing it from the Order Collection. To keep the listing short and focused on the relevant code, we only show “new code” in Listing 7.56, assuming the same basic code for setup and validation as addLineItem Part 1 in Listing 7.54.

Example 7.56. removeLineItem

<LINELENGTH>90</LINELENGTH>
@Override
public void removeLineItem(int customerId, int productId)
throws CustomerDoesNotExistException, OrderNotOpenException,
       ProductDoesNotExistException, NoLineItemsException,
       GeneralPersistenceException {
    try
    {
        // See addLineItem Part 1, steps 1-4 for setup and validation
        // 5. Create and populate the new LineItemId
        LineItemId lineItemId = new LineItemId();
        lineItemId.setOrderId(openOrder.getOrderId());
        lineItemId.setProductId(product.getProductId());
        // 6. Check to see if the line item already exists
        LineItem lineItem = null;
        try {
            lineItem = (LineItem) session.load(
                LineItem.class, lineItemId);
        }
        catch (ObjectNotFoundException e) {
            throw new NoLineItemsException(e);
        }
        // 7. Remove from Order
        openOrder.getLineitems().remove(lineItem);
        // 8. Commit the transaction
        session.getTransaction().commit();
    }
    catch (HibernateException e) {
        e.printStackTrace(System.out);
        throw new GeneralPersistenceException(
           "Unexpected Exception " + e);
    }
}

In this method, as with the preceding method, we load the AbstractCustomer, but this time we use its openOrder method to navigate to the open order to which the line item should be added. Likewise, we need to load the product instance that the LineItem will reference. There is an interesting difference in this method: If there is already a line item for this product, we do not add another—we merely update the quantity and amount. On the other hand, if a line item does not exist, we create it; then, as in the earlier example, after we have connected the LineItem to the Order, it becomes persistent implicitly.

Listing 7.57 shows the relevant details of how to submit an Order in Hibernate, referring back to Listing 7.54 again for code that you have already seen.

Example 7.57. Submit Order in Hibernate

<LINELENGTH>90</LINELENGTH>
public void submit(int customerId)
throws CustomerDoesNotExistException,
       OrderNotOpenException, NoLineItemsException,
       GeneralPersistenceException {
    try {
        // See addLineItem Part 1, steps 1-3 for setup and validation

        // 4. Throw an exception if the order has no line items
        if ((openOrder.getLineitems() == null) ||
                openOrder.getLineitems().size() == 0)
            throw new NoLineItemsException(
                "No line items for open order");
        // 5. Set the status to submitted
        openOrder.setStatus(Status.SUBMITTED);

        // 7. Set the open order in the customer to NULL and save
        customer.setOpenOrder(null);
        session.save(customer);

        // 8. Commit the transaction
        session.getTransaction().commit();
    }
    catch (HibernateException e) {
        throw new GeneralPersistenceException(
            "Unexpected Exception " + e);
    }
)

Packaging the Components

Because Hibernate POJOs are not specific to the Hibernate model, there are no special requirements placed on them with regard to packaging. One concern that you must address, however, is where you place your Hibernate configuration files. You need to make sure that the Configuration implementation knows where they are located; either you can do this by following the standard conventions provided by Hibernate (which states that configuration files are placed on the root of your classpath), or you can use the Configuration constructor shown earlier to explicitly point to the path where your configuration files are located.

For our end-to-end example, in Listing 7.58 we use the hibernate.cfg.xml configuration file, which ties together all the other mapping files that you’ve seen in the previous section.

Example 7.58. hibernate.cfg.xml Configuration File

<LINELENGTH>90</LINELENGTH>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
   "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
   "hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
    <session-factory>
        <property name="current_session_context_class">thread</property>
        <property name="hibernate.connection.driver_class">
            org.apache.derby.jdbc.ClientDriver
        </property>
        <property name="hibernate.connection.url">
            jdbc:derby://localhost:1527/PWTE;create=true;
        </property>
        <propertyname="hibernate.dialect">
            org.hibernate.dialect.DerbyDialect
        </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>
        <!-- Mapping files -->
        <mapping resource="Product.hbm.xml"/>
        <mapping resource="AbstractCustomer.hbm.xml"/>
        <mapping resource="LineItem.hbm.xml"/>
        <mapping resource="Order.hbm.xml"/>
        <mapping resource="Customer.hbm.xml"/>
    </session-factory>
</hibernate-configuration>

Finally, you need to ensure that the hibernate JAR file and the other JAR files on which the hibernate JAR file relies (described in the section “Other Dependencies,” at the beginning of the chapter) are on your class path.

Unit Testing

Hibernate is compatible with many unit testing frameworks, such as JUnit, DbUnit, and Cactus. The fact that it will work with either externally managed or internally managed transaction management and DataSources makes it easy to test code either inside a JEE container or outside a container. In fact, we have used JUnit and DbUnit in the internal testing of our code examples for this chapter.

Deploying to Production

Deploying a Hibernate application to production is usually a simple task. The most that one will often do is to make some changes to the Hibernate.cfg.xml file to ensure that the Hibernate Session is using the correct database drivers, DataSources, and/or database locations. This is a process that is easy to automate using scripting tools like Ant.

Summary

As you have seen in this chapter, the Hibernate “core” is a fully featured object-relational mapping solution that tries to provide the maximum amount of flexibility for dealing with special cases like difficult legacy schemas while still remaining simple to use for most cases. It facilitates integration with JEE application servers while also allowing the developer to use it from Java SE. It allows for sophisticated performance tuning through query optimization and caching techniques.

Besides having a JPA implementation, other Hibernate projects are being built on top of the “core” to enhance applications. Examples include the following:

  • Hibernate Shards Horizontal as a data partitioning framework

  • Hibernate Validator for Data integrity annotations and validation API

  • Hibernate Search: Hibernate integration with Lucene for indexing and querying data

  • Hibernate Tools Development tools for Eclipse and Ant

  • NHibernate: The NHibernate service for the .NET framework

  • JBoss Seam Framework for JSF, Ajax, and EJB 3.0/Java EE 5.0 applications

In summary, Hibernate is still a de facto standard in many places, and the project continues to grow.

Links to developerWorks

Links to developerWorks

A.7.1

Hibernate Simplifies Inheritance Mapping

 

This article illustrates using Hibernate to implement the various inheritance strategies discussed throughout this book.

 

www.ibm.com/developerworks/java/library/j-hibernate/

A.7.2

Using Hibernate to Persist Your Java Objects to IBM DB2 Universal Database

 

This article gives a simple Hibernate overview, illustrating the use of Hibernate with IBM DB2.

 

www.ibm.com/developerworks/db2/library/techarticle/0306bhogal/0306bhogal.html

A.7.3

Using Spring and Hibernate with WebSphere Application Server

 

This article shows the proper settings to configure when using Hibernate and Spring with WebSphere Application Server.

 

www.ibm.com/developerworks/websphere/techjournal/0609_alcott/0609_alcott.html

References

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset