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

10.6 Adding Hibernate

jPetStore uses a relatively straightforward architecture for providing database access. There is an interface layer that provides functional mapping to the DAOs themselves without worrying about actual implementation details. The specific DAOs vary based on the backend database; we'll be examining the ones targeting HSQLDB (Hypersonic SQL).

10.6.1 Existing Architecture

Let's look at how the Product class is managed. Product is the domain object that represents one item in the catalog.

package org.springframework.samples.jpetstore.domain;
import java.io.Serializable;
public class Product implements Serializable {

  private String productId;
  private String categoryId;
  private String name;
  private String description;

  public String getProductId( ) { return productId; }
  public void setProductId(String productId) { this.productId = productId.trim( ); }

  public String getCategoryId( ) { return categoryId; }
  public void setCategoryId(String categoryId) { this.categoryId = categoryId; }

  public String getName( ) { return name; }
  public void setName(String name) { this.name = name; }

  public String getDescription( ) { return description; }
  public void setDescription(String description) { this.description = description;}

  public String toString( ) {
    return getName( );
  }

}

Its persistence is managed through an object that implements the ProductDao interface. A ProductDao must be able to load a specific product given its ID, or load a list of products either from a category or from a set of keywords.

public interface ProductDao {

  List getProductListByCategory(String categoryId) throws DataAccessException;
  List searchProductList(String keywords) throws DataAccessException;
  Product getProduct(String productId) throws DataAccessException;

}

There currently exists a class called SqlMapProductDao that looks up product information in Hypersonic SQL through SQL mapping files.

10.6.2 Hibernate Mappings for Existing Domain Objects

To replace this architecture with one based on Hibernate, we first have to create mapping files that define the relationship between the domain objects and the database. Looking again at Product, we'll create a mapping file called Product.hbm.xml which looks like:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
    "-//Hibernate/Hibernate Mapping DTD 2.0//EN"
    "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">

<hibernate-mapping
    package="org.springframework.samples.jpetstore.domain">

    <class name="Product" table="product">
        <id name="productId"
            column="productId"
            type="string">
            <generator class="native"/>
        </id>
        <property name="categoryId" column="category" type="string"/>
        <property name="name" column="name" type="string"/>
        <property name="description" column="description" type="string"/>
    </class>

</hibernate-mapping>

In the mapping file, we first identify the package and particular class (org.springframework.samples.jpetstore.domain.Product) that we are mapping. We have to tell it what table to map to ("product", in this case) and then map the individual properties of the domain object to the columns in the table. This file needs to be saved somewhere on the class path; we'll create a new folder in the project structure called "hibernate" to hold our map files and our new DAOs.

10.6.3 Hibernate DAOs

The next step is to create a DAO that uses Hibernate as the persistence layer instead of the SQL mappings used in the original version. The new DAO needs to implement the ProductDao interface, just like the original DAO. However, the implementation of that interface will be totally different.

Here is the code for the new DAO:

public class HibernateProductDao implements ProductDao {
    SessionFactory factory;
    Configuration cfg;

    public HibernateProductDao( ) {
        try {
            cfg = new Configuration( ).addClass(
               org.springframework.samples.jpetstore.domain.Product.class);
            factory = cfg.buildSessionFactory( );
        } catch (Exception ex) {
            System.out.println("Hibernate configuration failed: " + ex);
        }
    }

    public List getProductListByCategory(String categoryId) 
       throws DataAccessException {
        List results = null;
        try {
            Session session = factory.openSession( );
            results = session.find("from product where product.category = ?",
                                    categoryId, Hibernate.STRING);
           session.close( );
        } catch (Exception ex) {
            System.out.println("Failed to connect to database:" + ex);
        }
         return results;
    }

    public List searchProductList(String keywords) throws DataAccessException {
        return null;
    }

    public Product getProduct(String productId) throws DataAccessException {
        Product p = null;
        try {
            Session session = factory.openSession( );
            p = (Product)session.load(Product.class, productId);
           session.close( );
        } catch (Exception ex) {
            System.out.println("failed to connect to database: " + ex);
            p = null;
        }

        return p;
    }

}

First, we need a way to interact with Hibernate. As shown in Chapter 7, we need to create a Hibernate SessionFactory and use it to get a Session with which to interact with the database. The DAO's constructor instantiates a new Hibernate configuration, loading the mapping file from the class path based on the name of the class added to the configuration. Then, it gets the SessionFactory from the Configuration.

Each method uses the SessionFactory to open a new Session with the database. The getProduct method is the most straightforward; first, we get the Session. Then, we ask the session to load an instance of the Product class, given its productId. Note that the result from the session.load( ) call is of type Object, which we have to cast to Product. Finally, we close the Session. Hibernate handles all the SQL commands, looking up the mapping files, matching the productId to the right column in the table, populating all the fields, everything.

The getProductListByCategory( ) method is less straightforward; it takes a categoryId and returns a List of all the products that match that category. In this case, we can't rely on the built-in SQL generation; we have to create our own query. Again, we first grab a Session from the SessionFactory, then use the session.find( ) method, which returns a List of Objects. Find takes three parameters in this case: the HSQL query (which contains a placeholder for a query parameter, marked with a "?"), the value to fill into the query parameter, and the type of that parameter.

As shown in Chapter 7, HSQL (Hibernate SQL) queries look a lot like regular SQL statements, except here we left off the "SELECT [values]" part of the statement, since Hibernate will fill those in for us based on the mapping. This method will now look up all the rows in the Product table where categoryId equals the value passed in to the method, and create one instance of Product for each row in the resultset. All the product instances are placed in a List and returned.

The final method of the DAO, searchProductList, would be a lot more complex, but luckily, we don't have to implement it. Since we have already replaced the original search functionality with the Simple Spider, this method will never be called now, so we simply return null (we have to do something, since the ProductDao interface still mandates its inclusion).

To finish out the new architecture, we just repeat these steps for each of the remaining five domain objects. Each gets a mapping file and an implementation of the appropriate DAO interface.

10.6.4 Changing the Application Configuration

In order to get the new DAOs working with jPetStore, we need to modify some configuration files. First, we'll need to create the global hibernate.properties file, which tells Hibernate which database to use and how to use it. jPetStore is currently configured to use a local instance of Hypersonic SQL, with a username of "sa" and a blank password (NEVER do this in a production environment). The hibernate.properties file looks like this:

hibernate.connection.driver_class = org.hsqldb.jdbcDriver
hibernate.connection.url = jdbc:hsqldb:hsql://localhost:9002
hibernate.connection.username = sa
hibernate.connection.password =
hibernate.dialect=net.sf.hibernate.dialect.HSQLDialect
hibernate.show_sql=true

This file should be saved in the project root file, next to the other global configuration files. Hibernate will look for it by name.

Next, open up jPetStore's dataAccessContext-*.xml files (one is dataAccessContext-jta.xml and the other is dataAccessContext-local.xml). In each, there is a section that mapes the DAOs for the project. Change each mapping to point to the new DAO, and eliminate the now unnecessary properties. For example, the original mapping for ProductDao was:

<bean id="productDao" class="org.springframework.samples.jpetstore.dao.ibatis.
                      SqlMapProductDao">
        <property name="dataSource"><ref local="dataSource"/></property>
        <property name="sqlMap"><ref local="sqlMap"/></property>
</bean>

This now becomes:

<bean id="productDao" class="org.springframework.samples.jpetstore.dao.hibernate.
HibernateProductDao"/>

We can eliminate the properties because the Hibernate versions of the DAOs do not require any configuration information to be passed in by the controller; Hibernate manages those issues for us.

Once you have successfully changed all the DAO references, the last remaining piece is to include the necessary jar files in your class path. Hibernate requires the following jars: hibernate2.jar, cglib2.jar, ehcache.jar, commons-collections.jar, dom4j.jar, and jta.jar (all of which are included in the Hibernate download).

10.6.5 Spring's Built-In Hibernate Support

Now that you have seen the explicit way to do things, let's briefly take a look at the supporting infrastructure Spring provides for Hibernate. Spring, through its "inversion of control" architecture, can fully manage the creation of the SessionFactory for you. In addition, it provides a new class, HibernateDaoSupport, which allows your application-specific DAOs to reuse standard, template-derived calls for interacting with the datasource.

To set it up, you need to change your DAOs to extend HibernateDaoSupport. So, this:

public class HibernateProductDao implements ProductDao

becomes:

public class HibernateProductDao extends HibernateDaoSupport implements ProductDao

Then add the following code to enable Spring to pass in a SessionFactory:

private SessionFactory sessionFactory;
public void setSessionFactory(SessionFactory sessionFactory) {
    this.sessionFactory = sessionFactory;
}

After adding this, your DAOs can use an object provided by HibernateDaoSupport called HibernateTemplate. This new class, accessed through the new getHibernateTemplate( ) method inherited from HibernateDaoSupport, exposes a series of helper methods for interacting with the database, such as load, save, update, saveOrUpdate, get, and find. Our ProductDao becomes a lot simpler:

public class HibernateProductDao extends HibernateDaoSupport implements ProductDao {
    private SessionFactory sessionFactory;

    public void setSessionFactory(SessionFactory sessionFactory) {
         this.sessionFactory = sessionFactory;
    }
    
    public HibernateProductDao( ) {
    }

    public List getProductListByCategory(String categoryId) {
        return getHibernateTemplate( ).find("from product where product.category = ?",
                                            categoryId, Hibernate.STRING);
    }

    public List searchProductList(String keywords) throws DataAccessExcetption {
        return null;
    }

    public Product getProduct(String ProductID) throws DataAccessException {
        return (Product) getHibernateTemplate( ).load(Product.class, productId);
    }

}

To configure all of this, you'll have to make some changes to your configuration files. You now have to add a property for the SessionFactory where you defined the ProductDao bean:

<bean id="productDao"
 class="org.springframework.samples.jpetstore.dao.hibernate.HibernateProductDao">
    <property name="sessionFactory"/>
    <ref bean="mySessionFactory"/>
</bean>

Then add a definition of the mySessionFactory bean:

<bean id="mySessionFactory"
 class="org.springframework.orm.hibernate.LocalSessionFactoryBean">
<property name="mappingResources">
<list>
    <value>product.hbm.xml</value>
</list>
<!-- etc. -->
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">net.sf.hibernate.dialect.HSQLDialect</prop>
</props>
</property>
<property name="dataSource">
    <ref bean="dataSource"/>
</property>
</bean>

Add as many entries to the mappingResources property as you have map files, and make sure that the dataSource property refers to your already-configured dataSource bean. With these minimal changes, your DAOs become much more standardized and compacted, and Spring handles all your SessionFactory and Session implementation details for you. You are free to focus, yet again, on the problem at hand rather than the supporting framework.

That's it! Once again, we've managed to replace an entire swath of existing code without touching the original codebase itself. We have simply added new classes to the project and changed some configuration settings, and voila! Hibernate.

10.6.6 Principles in Action

  • Keep it simple: domain objects remain unaware of persistence logic, Hibernate manages all configuration

  • Choose the right tools: Hibernate

  • Do one thing, and do it well: the domain model is focused on the business problem, the DAOs focus on data manipulation and are database-agnostic

  • Strive for transparency: domain model is completely unaware of persistence layer

  • Allow for extension: Spring configuration and IoC allow us to change persistence layers

    Previous Section  < Day Day Up >  Next Section