Java Servlet Programming, 2nd Edition > 9. Database Connectivity > 9.5 A Guestbook Servlet |
9.5 A Guestbook ServletTo help the database concepts sink in, let's look at a real servlet. Example 9-11 shows the code for a typical database-backed "guestbook" servlet. This servlet manages a web page where visitors can enter comments and see what comments others have written. A screen shot is provided in Figure 9-4. All the visitor comments are held inside a database. In accessing the database, this servlet uses several of the techniques covered in this chapter including using a connection pool, using a prepared statement, and using ContextProperties to read database configuration information from context init parameters. The servlet even extends com.oreilly.servlet.CacheHttpServlet from Chapter 4 to optimize its output performance. Figure 9-4. Guestbook outputExample 9-11. Please Sign Inimport java.io.*; import java.sql.*; import java.util.*; import javax.servlet.*; import javax.servlet.http.*; import com.oreilly.servlet.CacheHttpServlet; public class Guestbook extends CacheHttpServlet { static final String SELECT_ALL = "SELECT name, email, cmt, id FROM guestlist ORDER BY id DESC"; static final String INSERT = "INSERT INTO guestlist (id, name, email, cmt) " + "VALUES (?, ?, ?, ?)"; private long lastModified = 0; // Time database last changed private ConnectionPool pool; // Get a pointer to a connection pool public void init() throws ServletException { try { ServletContext context = getServletContext(); synchronized (context) { // A pool may already be saved as a context attribute pool = (ConnectionPool) context.getAttribute("pool"); if (pool == null) { // Construct a pool using our context init parameters // connection.driver, connection.url, user, password, etc pool = new ConnectionPool(new ContextProperties(context), 3); context.setAttribute("pool", pool); } } } catch (Exception e) { throw new UnavailableException( "Failed to fetch a connection pool from the context: " + e.getMessage()); } } // Display the current entries, then ask for a new entry public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { res.setContentType("text/html"); PrintWriter out = res.getWriter(); printHeader(out); printForm(out); printMessages(out); printFooter(out); } // Add a new entry, then dispatch back to doGet() public void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { handleForm(req, res); doGet(req, res); } private void printHeader(PrintWriter out) { out.println("<HTML><HEAD><TITLE>Guestbook</TITLE></HEAD>"); out.println("<BODY>"); } private void printForm(PrintWriter out) { out.println("<FORM METHOD=POST>"); // posts to itself out.println("<B>Please submit your feedback:</B><BR>"); out.println("Your name: <INPUT TYPE=TEXT NAME=name><BR>"); out.println("Your email: <INPUT TYPE=TEXT NAME=email><BR>"); out.println("Comment: <INPUT TYPE=TEXT SIZE=50 NAME=comment><BR>"); out.println("<INPUT TYPE=SUBMIT VALUE=\"Send Feedback\"><BR>"); out.println("</FORM>"); out.println("<HR>"); } // Read the messages from the database, and print private void printMessages(PrintWriter out) throws ServletException { String name, email, comment; Connection con = null; Statement stmt = null; ResultSet rs = null; try { con = pool.getConnection(); stmt = con.createStatement(); rs = stmt.executeQuery(SELECT_ALL); while (rs.next()) { name = rs.getString(1); if (rs.wasNull() || name.length() == 0) name = "Unknown user"; email = rs.getString(2); if (rs.wasNull() || email.length() == 0) name = "Unknown email"; comment = rs.getString(3); if (rs.wasNull() || comment.length() == 0) name = "No comment"; out.println("<DL>"); out.println("<DT><B>" + name + "</B> (" + email + ") says"); out.println("<DD><PRE>" + comment + "</PRE>"); out.println("</DL>"); } } catch (SQLException e) { throw new ServletException(e); } finally { try { if (stmt != null) stmt.close(); } catch (SQLException ignored) { } pool.returnConnection(con); } } private void printFooter(PrintWriter out) { out.println("</BODY>"); } // Save the new comment to the database private void handleForm(HttpServletRequest req, HttpServletResponse res) throws ServletException { String name = req.getParameter("name"); String email = req.getParameter("email"); String comment = req.getParameter("comment"); Connection con = null; PreparedStatement pstmt = null; try { con = pool.getConnection(); // Use a prepared statement for automatic string escaping pstmt = con.prepareStatement(INSERT); long time = System.currentTimeMillis(); pstmt.setString(1, Long.toString(time)); pstmt.setString(2, name); pstmt.setString(3, email); pstmt.setString(4, comment); pstmt.executeUpdate(); } catch (SQLException e) { throw new ServletException(e); } finally { try { if (pstmt != null) pstmt.close(); } catch (SQLException ignored) { } pool.returnConnection(con); } // Make note we have a new last modified time lastModified = System.currentTimeMillis(); } public long getLastModified(HttpServletRequest req) { return lastModified; // supports CacheHttpServlet } } The SQL statements for accessing the database are given at the top of the class in static final variables. Separating the SQL makes future modifications much easier. The init( ) method gets (or creates) a connection pool for this servlet to use. It first looks for a preexisting pool saved in as a ServletContext attribute under the name pool. If that attribute does not exist, the init( ) method constructs a new pool using the ContextProperties class to specify database configuration information and saves that pool to the context. To be honest, a connection pool isn't really needed for this servlet because there are no transactions and the bandwidth through a single reused Connection wouldn't be too great. In cases like this it makes sense to still use a connection pool but one that has the ability to return a shared Connection object for simultaneous use by multiple servlets. The doGet( ) method prints the header, a form asking the user to comment, the previous comments retrieved from the database, and a footer. Techniques for more elegant HTML creation in cases like this are shown beginning with Chapter 14. For now this gets the job done. The printMessages( ) method uses the pool to get a Connection, then executes the SELECT_ALL query using a Statement retrieved from that Connection. For each row in the ResultSet it prints a <DL> entry. In the finally block, the Connection goes back into the pool. The doPost( ) method is called when a user submits comments using the form generated by doGet( ). This method calls handleForm( ) to store the comment inside the database, then dispatches to doGet( ) to render the page. The handleForm( ) method retrieves the parameter's name, email, and comment and uses a PreparedStatement and the INSERT statement to save the parameter information in the database. By using a PreparedStatement to save the strings we automatically escape any special characters. A timestamp for the comment is also saved to the database, to provide a way to sort the comments and for possible later display. At the bottom of the handleForm( ) method it sets lastModified to the current time. This allows the getLastModified( ) method to return the time the database was last updated. Because this servlet extends CacheHttpServlet, the last modified information will be used by the superclass to manage a cache of its output, changing the cache only after database updates.[1]
|