Java Web Programming with Eclipse contents
Last modified February 05, 2011 10:50 pm

back next

Data Access Objects

Objectives

References

Video

Overview

In the previous chapter we showed how to access the database directly from servlet code. This architecture is illustrated in the following diagram.

Servlet-based system with direct access to the database

Servlet-based system with direct access to the database

The problem with this approach is that code to interact with the database tends to grow and becomes mixed with logic that directly deals with solving the problem at hand. Also, the code that is used is similar from one instance to the next, giving rise to code duplication. To solve these problems, we improve the architecture by defining persistent classes with their corresponding data access objects (DAOs). This allows us to express application logic in terms of manipulation of business objects such as NewsItem and User rather than operating on separate data fields taken from columns in the database. The following diagram illustrates the revised arhitecture.

Servlet-based system with object-oriented approach to persistence

Servlet-based system with object-oriented approach to persistence

Normally, code devoted to managing the persistent state of objects comprises a large percentage of the total code. Because this type of code can be generalized, libraries have been developed that provide developers with a framework to implement object persistence in their applications. These frameworks are referred to as object-relational mapping (ORM) frameworks because they provide the means to move data represented as objects in code into, and out of, relational databases. Two well-known open source ORM frameworks include Hibernate and iBatis. Use of an ORM persistence framework is outside the scope of this book. In this book, we look at a simple approach to implementing object persistence. However, as a real application would grow, our simple approach would become more complex, so that adoption of an ORM framework would be more cost-effective in the long run.

In this section, you will define a class called NewsItem that represents individual news items that are stored in the database. The news item class is considered to be persistent because the news item data in the database continues to exist between restarts of the web application. It is also referred to as a business object, or domain object, because it corresponds directly to an entity in the problem space.

You will also define the class NewsItemDAO to provide a means to manage the persistent instances of the news item class. In general, data access object classes (DAOs) hide database-specific activities from the system and present an object-oriented interface to manage persistent data.

When thinking about DAOs, you should remember that each DAO is responsible for managing instances of a single class. For this reason, we append the initials DAO with the name of the class being managed. Thus, the class NewsItemDAO manages instances of class NewsItem.

In our architecture, persistent instances of a class are identified through a member variable called id, which corresponds to a primary key in the database. Therefore, to retrieve a specific instance of a class, you would typically provide the object's id to a find() method of the object's DAO.

Standard methods provided by a DAO include the following, where Object is replaced by whatever class the DAO manages.

Object find(Long id);        // returns null if object doesn't exist
List findAll();              // always returns a collection, but may be empty
void update(Object object);  // throws NotFoundException if object doesn't exist
void create(Object object);  // sets the object's id as a side effect
void delete(Object object);  // silently ignores already deleted objects

In addition to the above standard methods, additional methods may be needed to support the particular needs of an application. Typically, these will be variations of the find and findAll methods. The following are some examples.

User findByUsername(String username);
List findAllExpired();
List findAllExpiredByType(Type type);

Create NewsItem Class

To increase readability, we locate the persistent classes and their data access objects in package publisher.data. Create the class NewsItem in the publisher.data package and add the following member variables.

private Long id;
private String title;
private String url;

Use the following procedure to generate the getter and setter methods for these variables.

  1. Selecting Source - Generate Getters and Setters.
  2. Click the Select All button.
  3. Click the OK button.

Create DataAccessObject

We will place code that is common to all DAOs in a superclass called DataAccessObject. This will eliminate code duplication that would result otherwise. Create a class called DataAccessObject in package publisher.data with the contents of the following listing.

DataAccessObject.java

package publisher.data;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

import javax.sql.DataSource;

public class DataAccessObject {

   private static DataSource dataSource;
   private static Object idLock = new Object();

   public static void setDataSource(DataSource dataSource) 
   {
      DataAccessObject.dataSource = dataSource;
   }

   protected static Connection getConnection() 
   {
      try {
         return dataSource.getConnection();
      } catch (SQLException e) {
         throw new RuntimeException(e);
      }
   }

   protected static void close(Statement statement, Connection connection) 
   {
      close(null, statement, connection);
   }

   protected static void close(ResultSet rs, Statement statement,
         Connection connection) 
   {
      try {
         if (rs != null)
            rs.close();
         if (statement != null)
            statement.close();
         if (connection != null)
            connection.close();
      } catch (SQLException e) {
         throw new RuntimeException(e);
      }
   }

   protected static Long getUniqueId() {
      ResultSet rs = null;
      PreparedStatement statement = null;
      Connection connection = null;
      try 
      {
         connection = getConnection();
         synchronized (idLock) 
         {
            statement = connection.prepareStatement("select next_value from sequence");
            rs = statement.executeQuery();
            rs.first();
            long id = rs.getLong(1);
            statement.close();
            statement = connection.prepareStatement("update sequence set next_value = ?");
            statement.setLong(1, id + 1);
            statement.executeUpdate();
            statement.close();
            return new Long(id);
         }
      }
      catch (SQLException e) 
      {
         throw new RuntimeException(e);
      }
      finally
      {
         close(rs, statement, connection);
      }
   }
}

The data access object class given above contains two member variables: id and idLock. We can place the variable id in this parent class because we will consistently follow the pattern of using an id in every persistent class that we create. Recall that this id is the surrogate primary key used in the database table that stores instances of the persistent class.

The idLock variable is declared as static, which means that a single instance of idLock is available at all times and is shared by all instances of the data access object class and its subclass instances. The idLock variable is used to lock the sequence table in the database so that only a single thread may access the table at one time. If you look inside the getUniqueId method, you can see that idLock is used as an argument to a synchronized statement. The synchronized statement allows only a single thread to enter the code block it contains. Contending threads are kept waiting and allowed to enter and exit one at a time.

To understand why this is necessary, suppose for a moment that we omit the synchronization and let threads execute statements in getUniqueId without regard to the presence of other threads. Suppose that thread A executes the query to get the value of next_value, which is 1000. However, before it gets to write the next value 1001 into the database thread A goes to sleep and thread B enters getUniqueId. Thread B also reads next_value from the database, which is still 1000. Thread B then updates next_value in the database to 1001. When B sleeps and A wakes up, A will redundently update next_value to 1001 in the database. So far, there is no runtime error, however A and B are in the process of creating separate news item instances with the same id, which they intend to use as primary key values for 2 different rows in the the news item table. The first thread that inserts its data into the database will succeed, but the second thread that attempts an insert will fail because it will violate the primary key constraint of uniqueness by using a value for id that already exists in the table. This is a runtime error that will generate an exception.

Our solution for generating unique primary key values will only work in systems that are deployed as single instances. If multple instances of the systems are deployed in order to handle levels of traffic that a single instance can not handle, then the idLock variable will not be shared between instances, which could result in generation of non-unique keys. In this situation, you need to use a different mechanism. This book does not cover these alternatives.

After creating the data access object class, you need to add the following statement to the contextInitialized method of the Init class, so that a data source reference can be passed into the data access object class when the application initializes. Make sure that this statement follows the line that sets the dataSource variable.

DataAccessObject.setDataSource(dataSource);

You need to organize imports in order to generate an import for the DataAccessObject class. While your at it, you should also remove the following line from Init, since direct access to the data source is being removed from servlets and being kept wholely within the data access objects.

NewsFeedServlet.setDataSource(dataSource);

Create NewsItemDAO

In this section we create the news item DAO class. For this purpose, create a class called NewsItemDAO in the publisher.data package that extends DataAccessObject.

At this point, we will add functionality that will be used by a revised version of the news feed servlet. Add the following implementation of the find method to the news item DAO class.

public NewsItem find(Long id) {
   ResultSet rs = null;
   PreparedStatement statement = null;
   Connection connection = null;
   try {
      connection = getConnection();
      String sql = "select * from news_item where id=?";
      statement = connection.prepareStatement(sql);
      statement.setLong(1, id.longValue());
      rs = statement.executeQuery();
      if (!rs.next()) {
         return null;
      }
      return read(rs);
   }
   catch (SQLException e) {
      throw new RuntimeException(e);
   }
   finally {
      close(rs, statement, connection);
   }
}

Organize imports, and select the following class among the alternatives.

The code does not yet compile because the read method is not yet defined. We will define this later.

The find method given above takes a long id as an argument and returns an instance of NewsItem. This method will execute the following SQL select command to locate the row within the news_item table that contains the id passed into the find method.

select * from news_item where id=?

The question mark in the above SQL statement is replaced by the value of the id passed into find by the following command.

statement.setLong(1, id.longValue());

The first argument in the setLong method given above is an integer n that refers to the nth question mark in the SQL statement that was used to create the prepared statement (not including question marks that may occur inside data values). (Note that numbering of question marks starts at 1 and not 0.) We pass in the value of the id so that the where clause attached to the select statement is narrowed to only those rows with id field equal to the one we specify. Because id is a primary key, it contains a unque value, so the table resulting from the select command will have either zero rows or one row.

If the resulting table (returned as a ResultSet) contains zero rows, it means that a news item with id equal to the given id does not exist. In this case, the find method returns null.

If the resulting table contains one row, then the news item with the given id exists. We therefore create an instance of the news item class and set its attributes according to the values found in the returned row. This job is handled by a separate read method. The reason we place the operations into a separate method is that we will need to carry out the same operations for other methods in the news item DAO. By placing these common operations in their own read method, we reduce code duplication. Minimizing code duplication is important because it makes the program easier to manage, since changes to common functionality only need to by made in one place.

The following listing provides the implementation of the read method, which you should add to the news item DAO class.

private NewsItem read(ResultSet rs) throws SQLException {
   Long id = new Long(rs.getLong("id"));
   String title = rs.getString("title");
   String url = rs.getString("url");
   NewsItem newsItem = new NewsItem();
   newsItem.setId(id);
   newsItem.setTitle(title);
   newsItem.setUrl(url);
   return newsItem;
}

We declare the read method as private because only methods within the news item DAO will call read. If we give the method greater visibility by declaring it public, the code will still function as needed; however, we would be violating a widely accepted best practice of limiting visibility as much as possible. The benefit of following this practice is that it improves readability of the code. If we make the read method private, the reader of the code knows that only internal methods will call the read method. If we declared it as public, the reader of the code may be misled into thinking that code outside the class is calling into the read method.

The above find method will be used in the next chapter by various servlets to extract a single news item from the database. However, the news feed servlet that we are developing in this chapter needs to extract all news items from the database. For this purpose, add the following implementation of the findAll method to the news item DAO.

public List<NewsItem> findAll() {
   LinkedList<NewsItem> newsItems = new LinkedList<NewsItem>();
   ResultSet rs = null;
   PreparedStatement statement = null;
   Connection connection = null;
   try {
      connection = getConnection();
      String sql = "select * from news_item order by id";
      statement = connection.prepareStatement(sql);
      rs = statement.executeQuery();
      while (rs.next()) {
         NewsItem newsItem = read(rs);
         newsItems.add(newsItem);
      }
      return newsItems;
   }
   catch (SQLException e) {
      throw new RuntimeException(e);
   } 
   finally {
      close(rs, statement, connection);
   }
}

Organize imports, and select the following class among the alternatives.

The findAll method selects all the rows and columns of the news_item table and then populates a linked list with instances of the news item class and return this to the caller. If no news items a found, we return a linked list that is empty. Notice that for every row in the result set, we call the read method to extract column values and place them in a news item instance.

Modify the News Feed Servlet

The news feed servlet will no longer access the database directly. For this reason, you can remove from it the member variable dataSource and its corresponding set method. (Also, you need to remove the call to setDataSource in the Init class.)

Replace the try/catch block in the doGet method of the news feed servlet class with the following code that extracts the data through the NewsItem and NewsItemDAO classes.

List<NewsItem> newsItems = new NewsItemDAO().findAll();
Iterator<NewsItem> it = newsItems.iterator();
while (it.hasNext()) {
   NewsItem newsItem = (NewsItem) it.next();
   String title = newsItem.getTitle();
   String url = newsItem.getUrl();
   SyndEntry entry = new SyndEntryImpl();
   entry.setTitle(title);
   entry.setLink(url);
   entries.add(entry);
}

Organize imports and select the following classes from the alternatives.

Test

Reload the publisher application and verify that the application can still obtain the news feed from the publisher application by going to http://localhost:8080/website/home.

Exercises

(1) PubDate field

If you have not carried over the publication date field from your solution to the PubDate exercise in the database-driven web applications chapter, then incorporate it back into the publisher and website web applications.

(2) Description field

Modify the publisher application so that it contains a description field in the news_item table and includes this in the XML returned by the news item servlet.

The ROME library handles the description field a little differently from other fields such as title and link. The following is some example code to get started with.

String description = newsItem.getDescription();
SyndContent syndContent = new SyndContentImpl();
syndContent.setType("text/plain");
syndContent.setValue(description);
entry.setDescription(syndContent);

Modify the website application so that it displays a description for each new item.

Note: when you invoke getDescription on the entry object in the website application, you should also call getValue to extract a string version of the description object. So, your code will something like the following.

entry.getDescription().getValue()

(3) Stored Procedures

Modify the getUniqueId() method of the DataAcessObject class so that unique ids are generated by a stored procedure running in the database server.

Can the stored procedure be designed in such a way that server code does not need to synchronize access to the function? In other words, can we safely omit the code that implements mutual exclusion by synchronizing on idLock.

(4) Transactions

For a web site that has a lot of traffic it is possible that a single instance of the web application is not sufficient to handle the load. In this case, a load balancing server can distribute the load across multiple instances of the application running on their own servers. In this case, the getUniqueId() implementation that relies on synchronizing on the idLock will not work correctly because the idLock object is not shared across machines. The implementation in the previous exercise that relies on stored procedures is one possible solution. Another possible solution is to use database transactions. For this exercise, modify the getUniqueId() method so that it reads and updates the sequence table within a database transaction. Include logic that repeatedly retries the transaction until it succeeds.

back next

Copyright 2007-2009 David Turner and Jinseok Chae. All rights reserved.