Chapter 14. Web Applications

Software engineers use layering to break down complex applications or systems. Layering is the organization of code into separate functional components that interact in some sequential and hierarchical way. Each layer sits on top of another layer and usually interacts only with the layers that are immediately above and below it. Each layer can be considered a single coherent component that can be used to provide many high-level services. You can work on each layer without knowing the details of other layers. By keeping the dependency between layers at a minimum, it's easy to refractor or replace single layer without affecting all the other layers. Adding too many layers can have an impact on performance.

Patterns are used to create layers and add structure to each layer. Popular patterns include the Model-View-Controller (MVC) pattern, Singleton pattern, Factory pattern, Observer pattern, and Decorator pattern, among many others. MVC is without doubt the most popular pattern used to create layered web applications. MVC splits code into three distinct components: data-access code, business-logic code, and presentation code (see Figure 14-1). By classifying code into these three layers, you achieve decoupling between the layers. Decoupling makes it easier to maintain one layer without requiring you to change other layers. The direction of dependencies is very important: the model doesn't depend on view/presentation or controller. If you're working in the model, you're unaware of the view.

The Model-View-Controller pattern splits code into three distinct roles

Figure 14-1. The Model-View-Controller pattern splits code into three distinct roles

This chapter demonstrates how to build a web application with this model in mind.

Creating a Controller for the Bookshop Web Application

Problem

How do you create a simple web application that uses a database as the persistent store?

Solution

To access a web application, you need to deploy it. Web applications are deployed on an application server. Common application servers include WebLogic, WebSphere, JBoss, and Tomcat. In this recipe, you install and deploy on a Tomcat server.

How It Works

Tomcat is an open source application server for running J2EE web applications. You can go to http://tomcat.apache.org/ and download Tomcat 5.5. Install it into a folder called C:Tomcat. Note that during the installation, you need to specify the Java Development Kit (JDK) installation path, not the Java Runtime Environment (JRE).

Creating a Dynamic Web Project

To develop a web application, you first create a dynamic web project BookShopWeb in Eclipse Web Tools Platform (WTP). To do so, select File

Creating a Dynamic Web Project
Create a dynamic web project in the Eclipse IDE

Figure 14-2. Create a dynamic web project in the Eclipse IDE

A wizard opens, in which you create your dynamic web project. For the project name, enter BookShopWeb. Use the default project directory, and choose Apache Tomcat as the target runtime (see Figure 14-3). Click the Finish button.

The New Dynamic Web Project wizard in the Eclipse IDE

Figure 14-3. The New Dynamic Web Project wizard in the Eclipse IDE

You need to configure an Apache Tomcat v5.5 runtime environment for this application. After you finish the wizard, copy the following jars to the WebContent/WEB-INF/lib directory. They're added to your project's CLASSPATH automatically:

${Hibernate_Install_Dir}/hibernate3.jar
${Hibernate_Install_Dir}/lib/antlr.jar
${Hibernate_Install_Dir}/lib/asm.jar
${Hibernate_Install_Dir}/lib/asm-attrs.jars
${Hibernate_Install_Dir}/lib/cglib.jar
${Hibernate_Install_Dir}/lib/commons-collections.jar
${Hibernate_Install_Dir}/lib/commons-logging.jar
${Hibernate_Install_Dir}/lib/dom4j.jar
${Hibernate_Install_Dir}/lib/ehcache.jar
${Hibernate_Install_Dir}/lib/jta.jar
${Hibernate_Install_Dir}/lib/log4j.jar
${Hsqldb_Install_Dir}/lib/hsqldb.jar
${Tomcat_Install_Dir}/common/lib/servlet-api.jar
${Tomcat_Install_Dir}/webapps/jsp-examples/WEB-INF/lib/standard.jar
${Tomcat_Install_Dir}/webapps/jsp-examples/WEB-INF/lib/jstl.jar

The web application you develop in this chapter only deals with the Book, Publisher, and Chapter persistent classes. Copy these three classes and the related mappings to your new project. Don't forget to copy the hibernate.cfg.xml, log4j.properties, and ehcache.xml files as well.

Configuring the Connection Pool

In previous recipes, you established a database connection each time a session was created and closed it at the end of the session. Because creating a physical database connection is very time consuming, you should use a connection pool to reuse your connections, especially for a multiuser environment.

As a web application server, Tomcat supports connection pooling. To configure a connection pool in Tomcat, you need to first copy the following JDBC driver to the ${Tomcat_Install_Dir}/common/lib directory:

${Hsqldb_Install_Dir}/lib/hsqldb.jar

After the first launch of Tomcat from WTP, a new project called Servers is created. You can modify the settings there for your Tomcat runtime. Open the server.xml configuration file, and locate the context BookShopWeb. You create a connection pool for this context by adding the following resource definition inside the <Context> node:

<Resource name="jdbc/BookShopDB"
type="javax.sql.DataSource"
driverClassName="org.hsqldb.jdbcDriver"
url="jdbc:hsqldb:hsql://localhost/BookShopDB"
username="sa"
password=""
maxActive="5"
maxIdle="2" />

Now the connection pool is ready to be used. You modify the hibernate.cfg.xml file to use this connection pool instead of creating a database connection each time. For the connection.datasource property, you provide the Java Naming and Directory Interface (JNDI) name of the connection pool configured with Tomcat. Also notice that you need to add the namespace java:/comp/env/ to the JNDI name:

<hibernate-configuration>
<session-factory>
<property name="connection.driver_class">org.hsqldb.jdbcDriver</property>
<property name="connection.url">jdbc:hsqldb:hsql://localhost/BookShopDB</property>
<property name="connection.username">sa</property>
<property name="connection.password"></property>
<property name="connection.datasource">java:/comp/env/jdbc/BookShopDB</property>
<property name="dialect">org.hibernate.dialect.HSQLDialect</property>
...
</session-factory>
</hibernate-configuration>

Developing an Online Bookshop

In this section, you implement the web-based online bookshop application using the typical MVC pattern. The purpose of this application is to demonstrate how to use Hibernate in a web environment, so it doesn't use any web frameworks such as Struts, Spring, and Tapestry. You implement it with JSPs, servlets, and taglibs, which is the standard way of developing J2EE web applications.

Creating a Global Session Factory

For an application using Hibernate as an object/relational mapping (ORM) framework, you create a global session factory and access it through a particular interface. Here, you use a static variable to store the session factory. It's initialized in a static block when this class is loaded for the first time:

public class HibernateUtil {

  private static final SessionFactory sessionFactory;

  static {
    try {
    Configuration configuration = new Configuration().configure();
    sessionFactory = configuration.buildSessionFactory();
    } catch (Throwable e) {
      e.printStackTrace();
    throw new ExceptionInInitializerError(e);
    }
  }

  public static SessionFactory getSessionFactory() {
    return sessionFactory;
  }
}

Listing Persistent Objects

The first function you implement for the online bookshop lists all the books available. You create a servlet BookListServlet to act as the controller and forward to the view booklist.jsp when you finish querying the books. Note that the list of books you get from the database is set as an attribute on the request object with the method setAttribute(). JSPs have implicit variables that help simplify code: the available variables are request, response, out, session, application, config, pageContext, and page. You set the list of books you receive from the database to the books attribute. And in the JSP, you read from this attribute:

public class BookListServlet extends HttpServlet {

  protected void doGet(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {
    SessionFactory factory = HibernateUtil.getSessionFactory();
    Session session = factory.openSession();
    try {
Query query = session.createQuery("from Book");
      List books = query.list();
      request.setAttribute("books", books);
    } finally {
      session.close();
    }
    RequestDispatcher dispatcher = request.getRequestDispatcher("booklist.jsp");
    dispatcher.forward(request, response);
  }
}

The view booklist.jsp is very simple. Its responsibility is to display the books queried by the servlet in a HTML table. At the moment, you only display the simple properties of books. Showing association properties is discussed later:

<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core_rt" %>
<html>
<head>
<title>Book List</title>
</head>
<body>
<table border="1">
<th>ISBN</th>
<th>Name</th>
<th>Publish Date</th>
<th>Price</th>
<c:forEach var="book" items="${books}">
<tr>
<td>${book.isbn}</td>
<td>${book.name}</td>
<td>${book.publishDate}</td>
<td>${book.price}</td>
</tr>
</c:forEach>
</table>
</body>
</html>

Figure 14-4 shows the list of books in the bookshop.

List of books in the bookshop

Figure 14-4. List of books in the bookshop

Updating Persistent Objects

Next, you allow administrators to update book details by clicking a hyperlink on a book's ISBN. This triggers another servlet, BookEditServlet, to display a form for editing, passing in the identifier of a book:

<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core_rt" %>
<html>
<head>
<title>Book List</title>
</head>
<body>
<table border="1">
<th>ISBN</th>
<th>Name</th>
<th>Publish Date</th>
<th>Price</th>
<c:forEach var="book" items="${books}">
<tr>
<td><a href="BookEditServlet?bookId=${book.id}">${book.isbn}</a></td>
<td>${book.name}</td>
<td>${book.publishDate}</td>
<td>${book.price}</td>
</tr>
</c:forEach>
</table>
</body>
</html>

The doGet() method of BookEditServlet is called when the user clicks the hyperlink on the ISBN. You load the book object from database according to the identifier passed in, and then you forward it to the view bookedit.jsp to show the form (see Figure 14-5):

public class BookEditServlet extends HttpServlet {

  protected void doGet(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {
    String bookId= request.getParameter("bookId");
    SessionFactory factory = HibernateUtil.getSessionFactory();
    Session session = factory.openSession();
    try {
      Book book = (Book) session.get(Book.class, Integer.parseInt(bookId));
      request.setAttribute("book", book);
    } finally {
      session.close();
    }
    RequestDispatcher dispatcher = request.getRequestDispatcher("bookedit.jsp");
    dispatcher.forward(request, response);
  }
}

The view bookedit.jsp shows a book's details in a series of form fields:

<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core_rt" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jstl/fmt_rt" %>
<html>
<head>
<title>Book Edit</title>
</head>
<body>
<form method="post">
<table>
<tr>
<td>ISBN</td>
<td><input type="text" name="isbn" value="${book.isbn}"></td>
</tr>
<tr>
<td>Name</td>
<td><input type="text" name="name" value="${book.name}"></td>
</tr>
<tr>
<td>Publish Date</td>
<td>
<input type="text" name="publishDate"
value="<fmt:formatDate value="${book.publishDate}" pattern="yyyy/MM/dd"/>">
</td>
</tr>
<tr>
<td>Price</td>
<td><input type="text" name="price" value="${book.price}"></td>
</tr>
<tr>
<td colspan="2">
<input type="hidden" name="bookId" value="${book. id}">
<input type="submit" value="Submit"></td>
</tr>
</table>
</form>
</body>
</html>
The form on which users can edit the properties of a book

Figure 14-5. The form on which users can edit the properties of a book

When the Submit button is clicked, the doPost() method of BookEditServlet is called to handle the form submission. You first load the book object from the database and then update its properties according to the request parameters. The user is then redirected to the book list page (see Figure 14-6):

public class BookEditServlet extends HttpServlet {
  ...
  protected void doPost(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {

    String bookId = request.getParameter("bookId ");
    String isbn = request.getParameter("isbn");
    String name = request.getParameter("name");
    String publishDate = request.getParameter("publishDate");
    String price = request.getParameter("price");
    SessionFactory factory = HibernateUtil.getSessionFactory();
    Session session = factory.openSession();
    Transaction tx = null;
try {
      tx = session.beginTransaction();
      Book book = (Book) session.get(Book.class, bookId);
      book.setIsbn(isbn);
      book.setName(name);
      book.setPublishDate(parseDate(publishDate));
      book.setPrice(Integer.parseInt(price));
      session.update(book);
      tx.commit();

    } catch (HibernateException e) {
      if (tx != null) tx.rollback();
      throw e;
    } finally {
      session.close();
    }

    response.sendRedirect("bookListServlet");
  }

  private Date parseDate(String date) {
    try {
      return new SimpleDateFormat("yyyy/MM/dd").parse(date);
    } catch (ParseException e) {
      return null;
    }
  }
}
The list of books after the price of the book Spring Recipes has been updated

Figure 14-6. The list of books after the price of the book Spring Recipes has been updated

Creating Persistent Objects

You should also let users create a new book. You first create a hyperlink on the book list page to add a new book. The target servlet is the same as for updating, but there is no book identifier to pass in:

<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core_rt" %>
<html>
<head>
<title>Book List</title>
</head>
<body>
<table border="1">
<th>ISBN</th>
<th>Name</th>
<th>Publish Date</th>
<th>Price</th>
<c:forEach var="book" items="${books}">
<tr>
<td>${book.isbn}</td>
<td>${book.name}</td>
<td>${book.publishDate}</td>
<td>${book.price}</td>
</tr>
</c:forEach>
<tr ><td colspan="4">
<a href="BookEditServlet">Add Book</a>
</td></tr>
</table>
</body>
</html>

The process of adding a new book is very similar to updating an existing book (see Figure 14-7). You can reuse the servlet BookEditServlet by checking whether the book identifier(isbn) is null. If it is, the action should be add; otherwise, it should be update. The generation of an identifier depends on the strategy you pick: you can let Hibernate generate one or have the application generate the identifier itself. The session.saveOrUpdate() method is very helpful for distinguishing the correct action:

public class BookEditServlet extends HttpServlet {

  protected void doGet(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {

    String bookId = request.getParameter("bookId");

    if (bookId != null) {
      SessionFactory factory = HibernateUtil.getSessionFactory();
      Session session = factory.openSession();
      try {
        Book book = (Book) session.get(Book.class, Integer.parseInt(bookId));
        request.setAttribute("book", book);
      } finally {
        session.close();
      }
    }

    RequestDispatcher dispatcher = request.getRequestDispatcher("bookedit.jsp");
    dispatcher.forward(request, response);
  }

  protected void doPost(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {

    String bookId = request.getParameter("bookId");
    String isbn = request.getParameter("isbn");
    String name = request.getParameter("name");
    String publishDate = request.getParameter("publishDate");
    String price = request.getParameter("price");
SessionFactory factory = HibernateUtil.getSessionFactory();
    Session session = factory.openSession();
    Transaction tx = null;

    try {
      tx = session.beginTransaction();
      Book book = new Book();
      if (bookId!=null && !bookId.equals("")) {
        book = (Book) session.get(Book.class, bookId);
      }
      book.setIsbn(isbn);
      book.setName(name);
      book.setPublishDate(parseDate(publishDate));
      book.setPrice(Integer.parseInt(price));
      session.saveOrUpdate(book);
      tx.commit();
    } catch (HibernateException e) {
      if (tx != null) tx.rollback();
      throw e;
    } finally {
      session.close();
    }

    response.sendRedirect("bookListServlet");
  }
}
The list of books after adding a new book

Figure 14-7. The list of books after adding a new book

Deleting Persistent Objects

The last book-management function allows the user to delete a book. You add a hyperlink to the last column of each row:

<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core_rt" %>
<html>
<head>
<title>Book List</title>
</head>
<body>
<table border="1">
Page 10 of 10
<th>ISBN</th>
<th>Name</th>
<th>Publish Date</th>
<th>Price</th>
<c:forEach var="book" items="${books}">
<tr>
<td>${book.isbn}</td>
<td>${book.name}</td>
<td>${book.publishDate}</td>
<td>${book.price}</td>
<td><a href="BookDeleteServlet?bookId=${book.isbn}">Delete</a></td>
</tr>
</c:forEach>
</table>
<a href="BookEditServlet">Add Book</a>
</body>
</html>

To delete a book from database, you must load it through the session first. This is because Hibernate needs to handle the cascading of associations. You may worry about the overhead of loading the object prior to deletion. But don't forget that you're caching your book objects in the second-level cache, so there isn't much impact on performance (see Figure 14-8):

public class BookDeleteServlet extends HttpServlet {

  protected void doGet(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {

    String bookId = request.getParameter("bookId");
    SessionFactory factory = HibernateUtil.getSessionFactory();
    Session session = factory.openSession();
    Transaction tx = null;
    try {
      tx = session.beginTransaction();
      Book book = (Book) session.get(Book.class, bookId);
      session.delete(book);
      tx.commit();
    } catch (HibernateException e) {
      if (tx != null) tx.rollback();
      throw e;
    } finally {
      session.close();
    }
    response.sendRedirect("bookListServlet");
  }
}
The list of books with the Delete link

Figure 14-8. The list of books with the Delete link

Creating a Data-Access Layer

Problem

What is a data-access layer? Why do you need a data-access layer, when the previous recipe appears to work fine? How do you add a data-access layer to the previous recipe?

Solution

A Data Access Object (DAO) layer abstracts the access to data from a persistent store. The persistent store can be a database or a file system. The DAO layer hides this persistent store from the application. A DAO layer may return a reference to objects instead of rows of fields from the database, which allows for a higher level of abstraction. It implements the mechanism to work with a data source. The business layer uses the interfaces provided by the DAO to interact with this data source.

How It Works

Let's see how to create a data-access layer.

Organizing Data Access in Data-Access Objects

Until now, you've put Hibernate-related code inside the servlets. In other words, you've mixed the presentation logic and the data-access logic. This is absolutely not a good practice. In a multitier application, the presentation logic and data-access logic should be separated for better reusability and maintainability. The DAO design pattern encapsulates the data-access logic. It manages the connection to the data source to store, retrieve, and update data. A good practice when using this pattern is to create one DAO for each persistent class and put all the data operations related to this class inside this DAO:

public class HibernateBookDao {

  public Book findById(Integer id) {

    SessionFactory factory = HibernateUtil.getSessionFactory();
    Session session = factory.openSession();
    try {
      Book book = (Book) session.get(Book.class, id);
      return book;
    } finally {
      session.close();
    }
  }

  public void saveOrUpdate(Book book) {
    SessionFactory factory = HibernateUtil.getSessionFactory();
    Session session = factory.openSession();
    Transaction tx = null;

    try {
      tx = session.beginTransaction();
      session.saveOrUpdate(book);
      tx.commit();
    } catch (HibernateException e) {
      if (tx != null) tx.rollback();
      throw e;
    } finally {
      session.close();
    }
   }

   public void delete(Book book) {
SessionFactory factory = HibernateUtil.getSessionFactory();
    Session session = factory.openSession();
    Transaction tx = null;

    try {
      tx = session.beginTransaction();
      session.delete(book);
      tx.commit();
    } catch (HibernateException e) {
      if (tx != null) tx.rollback();
      throw e;
    } finally {
      session.close();
    }
  }

  public List findAll() {

    SessionFactory factory = HibernateUtil.getSessionFactory();
    Session session = factory.openSession();
    try {
      Query query = session.createQuery("from Book");
      List books = query.list();
      return books;
    } finally {
      session.close();
    }
  }

  public Book findByIsbn(String isbn) {
    ...
  }

  public List findByPriceRange(int fromPrice, int toPrice) {
    ...
  }
}

According to object-oriented principles, you should program to the interface rather than to the implementation. So, you extract a BookDao interface and allow implementation other than the Hibernate one. The clients of this DAO should only know about the BookDao interface and needn't be concerned about the implementation:

public interface BookDao {

  public Book findById(Long id);
  public void saveOrUpdate(Book book);
  public void delete(Book book);
  public List findAll();
  public Book findByIsbn(String isbn);
public List findByPriceRange(int fromPrice, int toPrice);

}

public class HibernateBookDao implements BookDao {
  ...
}

Using Generic Data-Access Objects

Because different DAOs share common operations (such as findById, saveOrUpdate, delete, and findAll), you should extract a generic DAO for these operations to avoid code duplication:

public interface GenericDao {

  public Object findById(Long id);
  public void saveOrUpdate(Object book);
  public void delete(Object book);
  public List findAll();
}

Then, you create an abstract class HibernateGenericDao to implement this interface. You need to generalize the persistent class as a parameter of the constructor. Different subclasses pass in their corresponding persistent classes for concrete DAOs. For the findAll() method, you use a Criteria query because it can accept a class as a query target:

public abstract class HibernateGenericDao implements GenericDao {

  private Class persistentClass;

  public HibernateGenericDao(Class persistentClass) {

    this.persistentClass = persistentClass;
  }

  public Object findById(Long id) {

    SessionFactory factory = HibernateUtil.getSessionFactory();
    Session session = factory.openSession();
    try {
      Object object = (Object) session.get(persistentClass, id);
      return object;
    } finally {
      session.close();
    }
  }

  public void saveOrUpdate(Object object) {

    SessionFactory factory = HibernateUtil.getSessionFactory();
Session session = factory.openSession();
    Transaction tx = null;
    try {
      tx = session.beginTransaction();
      session.saveOrUpdate(object);
      tx.commit();
    } catch (HibernateException e) {
      if (tx != null) tx.rollback();
      throw e;
    } finally {
      session.close();
    }
  }

  public void delete(Object object) {

    SessionFactory factory = HibernateUtil.getSessionFactory();
    Session session = factory.openSession();
    Transaction tx = null;
    try {
      tx = session.beginTransaction();
      session.delete(object);
      tx.commit();
    } catch (HibernateException e) {
      if (tx != null) tx.rollback();
      throw e;
    } finally {
      session.close();
    }
  }

  public List findAll() {

    SessionFactory factory = HibernateUtil.getSessionFactory();
    Session session = factory.openSession();
    try {
      Criteria criteria = session.createCriteria(persistentClass);
      List objects = criteria.list();
      return objects;
    } finally {
      session.close();
    }
  }
}

For the Book persistent class, you can simplify BookDao and HibernateBookDao as follows:

public interface BookDao extends GenericDao {

  public Book findByIsbn(String isbn);
  public List findByPriceRange(int fromPrice, int toPrice);
}

public class HibernateBookDao extends HibernateGenericDao implements BookDao {

  public HibernateBookDao() {
    super(Book.class);
  }

  public Book findByIsbn(String isbn) {
    ...
  }

  public List findByPriceRange(int fromPrice, int toPrice) {
    ...
  }
}

Using a Factory to Centralize DAO Retrieval

Another problem when you use DAOs concerns their retrieval. Keep in mind that the creation of DAOs should be centralized for ease of implementation switching. Here, you apply an object-oriented design pattern called Abstract Factory to create a DaoFactory for the central point of DAO creation:

public abstract class DaoFactory {

  private static DaoFactory instance = new HibernateDaoFactory();

  public static DaoFactory getInstance() {
    return instance;
  }

  public abstract BookDao getBookDao();
}

public class HibernateDaoFactory extends DaoFactory {

  public BookDao getBookDao() {
    return new HibernateBookDao();
  }
}

Now that the DAOs and factory are ready, you can simplify your servlets as follows. Note that there is no longer any Hibernate-related code in the servlets:

public class BookListServlet extends HttpServlet {

  protected void doGet(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {
    BookDao dao = DaoFactory.getInstance().getBookDao();
List books = dao.findAll();
    request.setAttribute("books", books);
    RequestDispatcher dispatcher = request.getRequestDispatcher("booklist.jsp");
    dispatcher.forward(request, response);
  }
}

public class BookEditServlet extends HttpServlet {

  protected void doGet(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {
    String bookId = request.getParameter("bookId");
    if (bookId != null) {
      BookDao dao = DaoFactory.getInstance().getBookDao();
      Book book = (Book) dao.findById(Long.parseLong(bookId));
      request.setAttribute("book", book);
    }
    RequestDispatcher dispatcher = request.getRequestDispatcher("bookedit.jsp");
    dispatcher.forward(request, response);
  }

  protected void doPost(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {

    String bookId = request.getParameter("bookId");
    String isbn = request.getParameter("isbn");
    String name = request.getParameter("name");
    String publishDate = request.getParameter("publishDate");
    String price = request.getParameter("price");
    BookDao dao = DaoFactory.getInstance().getBookDao();
    Book book = new Book();
    if (!bookId.equals("")) {
      book = (Book) dao.findById(Long.parseLong(bookId));
    }
    book.setIsbn(isbn);
    book.setName(name);
    book.setPublishDate(parseDate(publishDate));
    book.setPrice(Integer.parseInt(price));
    dao.saveOrUpdate(book);
    response.sendRedirect("bookListServlet");
  }
}

public class BookDeleteServlet extends HttpServlet {

  protected void doGet(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {

    String bookId = request.getParameter("bookId");
    BookDao dao = DaoFactory.getInstance().getBookDao();
    Book book = (Book) dao.findById(Long.parseLong(bookId));
dao.delete(book);
    response.sendRedirect("bookListServlet");
  }
}

Navigating Lazy Associations

Suppose you want to include the publisher and chapter information on the book-editing page, but for viewing only:

<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core_rt" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jstl/fmt_rt" %>
<html>
<head>
<title>Book Edit</title>
</head>
<body>
<form method="post">
<table>
...
<tr>
<td>Publisher</td>
<td>${book.publisher.name}</td>
</tr>
<tr>
<td>Chapters</td>
<td>
<c:forEach var="chapter" items="${book.chapters}">${chapter.title}<br></c:forEach>
</td>
</tr>
...
</table>
<input type="hidden" name="bookId" value="${book.id}">
</form>
</body>
</html>

Because the two associations are lazy, you get a lazy initialization exception when accessing this page. To avoid this exception, you need to initialize the associations explicitly. To do so, create a new findById() method to distinguish the new association from the original one:

public interface BookDao extends GenericDao {
  ...
  public Book findWithPublisherAndChaptersById(Long id);
}

public class HibernateBookDao extends HibernateGenericDao implements BookDao {
  ...
  public Book findWithPublisherAndChaptersById(Long id) {
SessionFactory factory = HibernateUtil.getSessionFactory();
    Session session = factory.openSession();
    try {
      Book book = (Book) session.get(Book.class, id);
      Hibernate.initialize(book.getPublisher());
      Hibernate.initialize(book.getChapters());
      return book;
    } finally {
      session.close();
    }
  }
}

public class BookEditServlet extends HttpServlet {
  ...
  protected void doGet(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {
    String bookId = request.getParameter("bookId");
    if (bookId != null) {
      BookDao dao = DaoFactory.getInstance().getBookDao();
      Book book = (Book)dao.findWithPublisherAndChaptersById(Long.parseLong(bookId));
      request.setAttribute("book", book);
    }
    RequestDispatcher dispatcher = request.getRequestDispatcher("bookedit.jsp");
    dispatcher.forward(request, response);
  }
}

Using the Open Session in View Pattern

Is it much trouble to initialize lazy associations explicitly? How can you ask the associations to be initialized on demand—when they're accessed for the first time?

The root cause of the lazy initialization exception is that the session is closed before the lazy association is first accessed during the rendering of the JSP. If you can keep the session open for the entire request-handling process, including servlet processing and JSP rendering, the exception should be able to resolve. To implement this idea, you can use a filter in a J2EE web application.

A filter is code that is invoked before the user request reaches the servlet or JSP. You can attach a filter to one or more servlets or JSPs; it looks at the request object and validates the request before letting the request pass to the servlet or JSP. You open and close the Hibernate session in a filter such that it's accessible for the entire request-handling process. This is called the Open Session in View pattern.

Hibernate provides the factory.getCurrentSession() method to retrieve the current session. A new session is opened the first time you call this method and closed when the transaction is finished, regardless of whether it's committed or rolled back. But what does current session mean? You need to tell Hibernate that this is the session bound with the current thread:

<hibernate-configuration>
<session-factory>
...
<property name="current_session_context_class">thread</property>
...
</session-factory>
</hibernate-configuration>

public class HibernateSessionFilter implements Filter {
  ...
  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
      throws IOException, ServletException {

    SessionFactory factory = HibernateUtil.getSessionFactory();
    Session session = factory.getCurrentSession();
    try {
      session.beginTransaction();
      chain.doFilter(request, response);
      session.getTransaction().commit();
    } catch (Throwable e) {
      if (session.getTransaction().isActive()) {
        session.getTransaction().rollback();
      }
      throw new ServletException(e);
    }
  }
}

To apply this filter to your application, modify web.xml to add the following filter definition and mapping:

<filter>
<filter-name>HibernateSessionFilter</filter-name>
<filter-class>com.metaarchit.bookshop.HibernateSessionFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>HibernateSessionFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

An arbitrary object can access the current session through the factory.getCurrentSession() method. This returns the session bound with the current thread. Note that you can omit the transaction-management code because the transaction is committed by the filter if no exception is thrown:

public abstract class HibernateGenericDao implements GenericDao {
  ...
  public Object findById(Long id) {
    SessionFactory factory = HibernateUtil.getSessionFactory();
    Session session = factory.getCurrentSession();
    Object object = (Object) session.get(persistentClass, id);
    return object;
  }

  public void saveOrUpdate(Object object) {
    SessionFactory factory = HibernateUtil.getSessionFactory();
    Session session = factory.getCurrentSession();
session.saveOrUpdate(object);
  }

  public void delete(Object object) {
    SessionFactory factory = HibernateUtil.getSessionFactory();
    Session session = factory.getCurrentSession();
    session.delete(object);
  }

  public List findAll() {
    SessionFactory factory = HibernateUtil.getSessionFactory();
    Session session = factory.getCurrentSession();
    Criteria criteria = session.createCriteria(persistentClass);
    List objects = criteria.list();
    return objects;
  }
}

Summary

In this chapter, you've learned how to create a web application. You can now see your book list, add books to that list from a web browser, and update book information from the browser. You've also learned the importance of creating multilayered applications and maintaining loose coupling between layers.

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

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