16.2. Creating a persistence layer

Mixing data-access code with application logic violates the emphasis on separation of concerns. There are several reasons why you should consider hiding the Hibernate calls behind a facade, the so-called persistence layer:

  • The persistence layer can provide a higher level of abstraction for data-access operations. Instead of basic CRUD and query operations, you can expose higher-level operations, such as a getMaximumBid() method. This abstraction is the primary reason why you want to create a persistence layer in larger applications: to support reuse of the same non-CRUD operations.

  • The persistence layer can have a generic interface without exposing actual implementation details. In other words, you can hide the fact that you're using Hibernate (or Java Persistence) to implement the data-access operations from any client of the persistence layer. We consider persistence layer portability an unimportant concern, because full object/relational mapping solutions like Hibernate already provide database portability. It's highly unlikely that you'll rewrite your persistence layer with different software in the future and still not want to change any client code. Furthermore, consider Java Persistence as a standardized and fully portable API.

  • The persistence layer can unify data-access operations. This concern is related to portability, but from a slightly different angle. Imagine that you have to deal with mixed data-access code, such as Hibernate and JDBC operations. By unifying the facade that clients see and use, you can hide this implementation detail from the client.

If you consider portability and unification to be side effects of creating a persistence layer, your primary motivation is achieving a higher level of abstraction and the improved maintainability and reuse of data-access code. These are good reasons, and we encourage you to create a persistence layer with a generic facade in all but the simplest applications. It's again important that you don't overengineer your system and that you first consider using Hibernate (or Java Persistence APIs) directly without any additional layering. Let's assume you want to create a persistence layer and design a facade that clients will call.

There is more than one way to design a persistence layer facade—some small applications may use a single PersistenceManager object; some may use some kind of command-oriented design, and others mix data-access operations into domain classes (active record)—but we prefer the DAO pattern.

16.2.1. A generic data-access object pattern

The DAO design pattern originated in Sun's Java Blueprints. It's even used in the infamous Java Petstore demo application. A DAO defines an interface to persistence operations (CRUD and finder methods) relating to a particular persistent entity; it advises you to group together code that relates to persistence of that entity.

Using JDK 5.0 features such as generics and variable arguments, you can design a nice DAO persistence layer easily. The basic structure of the pattern we're proposing here is shown in figure 16.1.

Figure 16-1. Generic DAO interfaces support arbitrary implementations

We designed the persistence layer with two parallel hierarchies: interfaces on one side, implementations on the other side. The basic object-storage and -retrieval operations are grouped in a generic superinterface and a superclass that implements these operations with a particular persistence solution (we'll use Hibernate). The generic interface is extended by interfaces for particular entities that require additional business-related data-access operations. Again, you may have one or several implementations of an entity DAO interface.

Let's first consider the basic CRUD operations that every entity shares and needs; you group these in the generic superinterface:

public interface GenericDAO<T, ID extends Serializable> {

    T findById(ID id, boolean lock);

    List<T> findAll();

    List<T> findByExample(T exampleInstance,
                          String... excludeProperty);

    T makePersistent(T entity);

    void makeTransient(T entity);

    void flush();

    void clear();

}

The GenericDAO is an interface that requires type arguments if you want to implement it. The first parameter, T, is the entity instance for which you're implementing a DAO. Many of the DAO methods use this argument to return objects in a type-safe manner. The second parameter defines the type of the database identifier—not all entities may use the same type for their identifier property. The second thing that is interesting here is the variable argument in the findByExample() method; you'll soon see how that improves the API for a client.

Finally, this is clearly the foundation for a persistence layer that works state-oriented. Methods such as makePersistent() and makeTransient() change an object's state (or many objects at once with cascading enabled). The flush() and clear() operations can be used by a client to manage the persistence context. You'd write a completely different DAO interface if your persistence layer were statement-oriented; for example if you weren't using Hibernate to implement it but only plain JDBC.

The persistence layer facade we introduced here doesn't expose any Hibernate or Java Persistence interface to the client, so theoretically you can implement it with any software without making any changes to client code. You may not want or need persistence layer portability, as explained earlier. In that case, you should consider exposing Hibernate or Java Peristence interfaces—for example, a findByCriteria(DetachedCriteria) method that clients can use to execute arbitrary Hibernate Criteria queries. This decision is up to you; you may decide that exposing Java Persistence interfaces is a safer choice than exposing Hibernate interfaces. However, you should know that while it's possible to change the implementation of the persistence layer from Hibernate to Java Persistence or to any other fully featured state-oriented object/relational mapping software, it's almost impossible to rewrite a persistence layer that is state-oriented with plain JDBC statements.

Next, you implement the DAO interfaces.

16.2.2. Implementing the generic CRUD interface

Let's continue with a possible implementation of the generic interface, using Hibernate APIs:

public abstract class
        GenericHibernateDAO<T, ID extends Serializable>
            implements GenericDAO<T, ID> {

    private Class<T> persistentClass;
    private Session session;

    public GenericHibernateDAO() {
        this.persistentClass = (Class<T>)
           ( (ParameterizedType) getClass().getGenericSuperclass() )
                .getActualTypeArguments()[0];
     }

    public void setSession(Session s) {
        this.session = s;
    }

    protected Session getSession() {
        if (session == null)
            session = HibernateUtil.getSessionFactory()
                                    .getCurrentSession();
        return session;
    }

    public Class<T> getPersistentClass() {
        return persistentClass;
    }

    ...

So far this is the internal plumbing of the implementation with Hibernate. In the implementation, you need access to a Hibernate Session, so you require that the client of the DAO injects the current Session it wants to use with a setter method. This is mostly useful in integration testing. If the client didn't set a Session before using the DAO, you look up the current Session when it's needed by the DAO code.

The DAO implementation must also know what persistent entity class it's for; you use Java Reflection in the constructor to find the class of the T generic argument and store it in a local member.

If you write a generic DAO implementation with Java Persistence, the code looks almost the same. The only change is that an EntityManager is required by the DAO, not a Session.

You can now implement the actual CRUD operations, again with Hibernate:

@SuppressWarnings("unchecked")
public T findById(ID id, boolean lock) {
    T entity;
    if (lock)
        entity = (T) getSession()
            .load(getPersistentClass(), id, LockMode.UPGRADE);
    else
        entity = (T) getSession()
            .load(getPersistentClass(), id);
    return entity;
}

@SuppressWarnings("unchecked")
public List<T> findAll() {
    return findByCriteria();
}

@SuppressWarnings("unchecked")
public List<T> findByExample(T exampleInstance,
                             String... excludeProperty) {
    Criteria crit =
        getSession().createCriteria(getPersistentClass());
    Example example =  Example.create(exampleInstance);
    for (String exclude : excludeProperty) {
        example.excludeProperty(exclude);
    }
    crit.add(example);
    return crit.list();
}

@SuppressWarnings("unchecked")
public T makePersistent(T entity) {
    getSession().saveOrUpdate(entity);
    return entity;
}

public void makeTransient(T entity) {
    getSession().delete(entity);
}

public void flush() {
    getSession().flush();
}

public void clear() {
    getSession().clear();
}

/**
 * Use this inside subclasses as a convenience method.
 */
@SuppressWarnings("unchecked")
protected List<T> findByCriteria(Criterion... criterion) {

    Criteria crit =
        getSession().createCriteria(getPersistentClass());
    for (Criterion c : criterion) {
        crit.add(c);
    }
    return crit.list();
   }

}

All the data-access operations use getSession() to get the Session that is assigned to this DAO. Most of these methods are straightforward, and you shouldn't have any problem understanding them after reading the previous chapters of this book. The @SurpressWarning annotations are optional—Hibernate interfaces are written for JDKs before 5.0, so all casts are unchecked and the JDK 5.0 compiler generates a warning for each otherwise. Look at the protected findByCriteria() method: We consider this a convenience method that makes the implementation of other data-access operations easier. It takes zero or more Criterion arguments and adds them to a Criteria that is then executed. This is an example of JDK 5.0 variable arguments. Note that we decided not to expose this method on the public generic DAO interface; it's an implementation detail (you may come to a different conclusion).

An implementation with Java Persistence is straightforward, although it doesn't support a Criteria API. Instead of saveOrUpdate(), you use merge() to make any transient or detached object persistent, and return the merged result.

You've now completed the basic machinery of the persistence layer and the generic interface it exposes to the upper layer of the system. In the next step, you create entity-related DAO interfaces and implement them by extending the generic interface and implementation.

16.2.3. Implementing entity DAOs

Let's assume that you want to implement non-CRUD data-access operations for the Item business entity. First, write an interface:

public interface ItemDAO extends GenericDAO<Item, Long> {

    Bid getMaxBid(Long itemId);
    Bid getMinBid(Long itemId);

}

The ItemDAO interface extends the generic super interface and parameterizes it with an Item entity type and a Long as the database identifier type. Two data-access operations are relevant for the Item entity: getMaxBid() and getMinBid().

An implementation of this interface with Hibernate extends the generic CRUD implementation:

public class ItemDAOHibernate
        extends     GenericHibernateDAO<Item, Long>
        implements  ItemDAO {

    public Bid getMaxBid(Long itemId) {
        Query q = getSession().getNamedQuery("getItemMaxBid");
        q.setParameter("itemid", itemId);
        return (Bid) q.uniqueResult();
    }

    public Bid getMinBid(Long itemId) {
        Query q = getSession().getNamedQuery("getItemMinBid");
        q.setParameter("itemid", itemId);
        return (Bid) q.uniqueResult();
    }

}

You can see how easy this implementation was, thanks to the functionality provided by the superclass. The queries have been externalized to mapping metadata and are called by name, which avoids cluttering the code.

We recommend that you create an interface even for entities that don't have any non-CRUD data-access operations:

public interface CommentDAO extends GenericDAO<Comment, Long> {
    // Empty

}

The implementation is equally straightforward:

public static class CommentDAOHibernate
        extends GenericHibernateDAO<Comment, Long>
        implements CommentDAO {}

We recommend this empty interface and implementation because you can't instantiate the generic abstract implementation. Furthermore, a client should rely on an interface that is specific for a particular entity, thus avoiding costly refactoring in the future if additional data-access operations are introduced. You might not follow our recommendation, however, and make GenericHibernateDAO nonabstract. This decision depends on the application you're writing and what changes you expect in the future.

Let's bring this all together and see how clients instantiate and use DAOs.

16.2.4. Using data-access objects

If a client wishes to utilize the persistence layer, it has to instantiate the DAOs it needs and then call methods on these DAOs. In the previously introduced Hibernate web application use case, the controller and action code look like this:

public void execute() {

    Long itemId = ...           // Get value from request
    Long userId = ...           // Get value from request
    BigDecimal bidAmount = ...  // Get value from request

    // Prepare DAOs
    ItemDAO itemDAO = new ItemDAOHibernate();
    UserDAO userDAO = new UserDAOHibernate();

    // Load requested Item
    Item item = itemDAO.findById(itemId, true);

    // Get maximum and minimum bids for this Item
    Bid currentMaxBid = itemDAO.getMaxBid(itemId);
    Bid currentMinBid = itemDAO.getMinBid(itemId);

    // Load bidder
    User bidder = userDAO.findById(userId, false);

    try {

        Bid newBid = item.placeBid(bidder,
                                   bidAmount,
                                   currentMaxBid,
                                   currentMinBid);

        ...     // Place new Bid into request context

        ...     // Forward to success page

    } catch (BusinessException e) {
        ...     // Forward to appropriate error page
    }

}

You almost manage to avoid any dependency of controller code on Hibernate, except for one thing: You still need to instantiate a specific DAO implementation in the controller. One (not very sophisticated) way to avoid this dependency is the traditional abstract factory pattern.

First, create an abstract factory for data-access objects:

public abstract class DAOFactory {

    /**
     * Factory method for instantiation of concrete factories.
     */

    public static DAOFactory instance(Class factory) {
        try {
            return (DAOFactory)factory.newInstance();
        } catch (Exception ex) {
            throw new RuntimeException(
                       "Couldn't create DAOFactory: " + factory
                      );
        }
    }

    // Add your DAO interfaces here
    public abstract ItemDAO getItemDAO();
    public abstract CategoryDAO getCategoryDAO();
    public abstract CommentDAO getCommentDAO();
    public abstract UserDAO getUserDAO();
    public abstract BillingDetailsDAO getBillingDetailsDAO();
    public abstract ShipmentDAO getShipmentDAO();

}

This abstract factory can build and return any DAO. Now implement this factory for your Hibernate DAOs:

public class HibernateDAOFactory extends DAOFactory {

    public ItemDAO getItemDAO() {
        return (ItemDAO) instantiateDAO(ItemDAOHibernate.class);
    }

    ...

    private GenericHibernateDAO instantiateDAO(Class daoClass) {
        try {
            GenericHibernateDAO dao = (GenericHibernateDAO)
                                      daoClass.newInstance();
            return dao;
        } catch (Exception ex) {
            throw new RuntimeException(
                        "Can not instantiate DAO: " + daoClass, ex
                      );
        }
    }

    // Inline all empty DAO implementations

    public static class CommentDAOHibernate
            extends GenericHibernateDAO<Comment, Long>
            implements CommentDAO {}

    public static class ShipmentDAOHibernate
            extends GenericHibernateDAO<Shipment, Long>
            implements ShipmentDAO {}
    ...

}

Several interesting things happen here. First, the implementation of the factory encapsulates how the DAO is instantiated. You can customize this method and set a Session manually before returning the DAO instance.

Second, you move the implementation of CommentDAOHibernate into the factory as a public static class. Remember that you need this implementation, even if it's empty, to let clients work with interfaces related to an entity. However, nobody forces you to create dozens of empty implementation classes in separate files; you can group all the empty implementations in the factory. If in the future you have to introduce more data-access operations for the Comment entity, move the implementation from the factory to its own file. No other code needs to be changed—clients rely only on the CommentDAO interface.

With this factory pattern, you can further simplify how DAOs are used in the web application controller:

public void execute() {

    Long itemId = ...           // Get value from request
    Long userId = ...           // Get value from request
    BigDecimal bidAmount = ...  // Get value from request

    // Prepare DAOs
    DAOFactory factory = DAOFactory.instance(DAOFactory.HIBERNATE);
    ItemDAO itemDAO = factory.getItemDAO();
    UserDAO userDAO = factory.getUserDAO();

    // Load requested Item
    Item item = itemDAO.findById(itemId, true);

    // Get maximum and minimum bids for this Item
    Bid currentMaxBid = itemDAO.getMaxBid(itemId);
    Bid currentMinBid = itemDAO.getMinBid(itemId);

    // Load bidder
    User bidder = userDAO.findById(userId, false);

    try {
        ...
    }

}

The only dependency on Hibernate, and the only line of code that exposes the true implementation of the persistence layer to client code, is the retrieval of the DAOFactory. You may want to consider moving this parameter into your application's external configuration so that you can possibly switch DAOFactory implementations without changing any code.


Tip:

Mixing Hibernate and JDBC code in a DAO—Rarely do you have to use plain JDBC when you have Hibernate available. Remember that if you need a JDBC Connection to execute a statement that Hibernate can't produce automatically, you can always fall back with session.connection(). So, we don't think you need different and separate DAOs for a few JDBC calls. The issue with mixing Hibernate and plain JDBC isn't the fact that you sometimes may have to do it (and you should definitely expect that Hibernate won't solve 100 percent of all your problems) but that developers often try to hide what they did. There is no problem with mixed data-access code as long as it's properly documented. Also remember that Hibernate supports almost all SQL operations with native APIs, so you don't necessarily have to fall back to plain JDBC.


You've now created a clean, flexible, and powerful persistence layer that hides the details of data access from any client code. The following questions are likely still on your mind:

  • Do you have to write factories? The factory pattern is traditional and is used in applications that mostly rely on lookup of stateless services. An alternative (or sometimes complementary) strategy is dependency injection. The EJB 3.0 specification standardizes dependency injection for managed components, so we'll look at an alternative DAO wiring strategy later in this chapter.

  • Do you have to create one DAO interface per domain entity? Our proposal doesn't cover all possible situations. In larger applications, you may want to group DAOs by domain package or create deeper hierarchies of DAOs that provide more fine-grained specialization for particular subentities. There are many variations of the DAO pattern, and you shouldn't restrict your options with our recommended generic solution. Feel free to experiment, and consider this pattern a good starting point.

You now know how to integrate Hibernate in a traditional web application and how to create a persistence layer following best practices patterns. If you have to design and write a three-tier application, you need to consider a quite different architecture.

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

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