Richard G Baldwin (512) 223-4758, baldwin@austin.cc.tx.us, http://www2.austin.cc.tx.us/baldwin/

Servlets and the JDBC ResultSetMetaData Interface


Java Programming, Lecture Notes # 688, Revised 8/22/99.

Preface

Introduction

Sample Program

Interesting Code Fragments

Program Listing


Preface

Students in Prof. Baldwin's Advanced Java Programming classes at ACC will be responsible for knowing and understanding all of the material in this lesson beginning with the spring semester of 1999.

This lesson was originally written on January 9, 1999. The sample servlet was tested using the JDK 1.2 download package from JavaSoft along with the Java Servlet Development Kit (JSDK) 2.0 from JavaSoft. All tests were performed under Win95.

The servlet was tested using the servletrunner program that is included in the JSDK running as localhost on the same machine. The servlet was also tested using the JavaSoft Java Web Server 1.1.1 program running on a different machine on the network.

All tests required the use of the Windows version of the mSQL 2.0 database program from http://blnet.com/msqlpc/downloads.htm#win and the JDBC interface classes for mSQL and JDK 1.2 from http://www.Imaginary.com/Java/.

Introduction

The primary purpose of this lesson is to introduce you to the use of the JDBC ResultSetMetaData Interface. The lesson also illustrates the use of a servlet to access a database to retrieve data and send it back to a client.

Sample Program

The name of the sample program is Servlet05. The primary purpose of this program is to illustrate the use of the JDBC ResultSetMetaData interface, which provides a way to get information about the underlying structure of a ResultSet object at runtime.

This servlet performs a query on a database and then displays the contents of the resulting ResultSet object in an HTML table. This is accomplished without the servlet knowing anything about the contents of the ResultSet object in advance.

The raw text output from the servlet is shown below. Note that I manually inserted line breaks at certain critical points to make this material easier to read. The HTML doesn't care about line breaks in the raw text as long as the break appears at an appropriate place in the text.

<HTML>

<HEAD><TITLE=Servlet05</TITLE></HEAD>

<BODY>

com.imaginary.sql.msql.MsqlException: Table "servlet05Table" exists

<br>

Unable to create table<br><br>

Contents of Database<BR><BR>

<TABLE BORDER=2>

<TR><TH>firstName<TH>middleInitial<TH>lastName</TR>

<TR><TD>Michael<TD>J.<TD>Mouse</TR>

<TR><TD>John<TD>Q.<TD>Public</TR>

<TR><TD>Jane<TD>M.<TD>Doe</TR>

<TR><TD>Im<TD>A.<TD>Student</TR>

<TR><TD>Ur<TD>A.<TD>Student</TR>

</TABLE>

</BODY>

</HTML>

This servlet first produced the following preliminary information in the browser window.

com.imaginary.sql.msql.MsqlException: Table "servlet05Table" exists
Unable to create table

Contents of Database

After displaying the preliminary information, the servlet obtained a list of names from the database and formatted them into an HTML table as shown below.

firstName

middleInitial

lastName

Michael

J.

Mouse

John

Q.

Public

Jane

M.

Doe

Im

A.

Student

Ur

A.

Student

This is an upgrade to servlet named Servlet02 discussed in an earlier lesson. The upgrade is described by item 4 in following list.

This servlet performs the following operations:

  1. Get data from a client form.
  2. Access a database to store data obtained from the client form.
  3. Access the database to retrieve data.
  4. Format the retrieved data into an HTML table and send it back to the client.

The client has the ability to either cause the existing table to be deleted and add data to a new table, or to simply add new data to an existing table. The data provided by the client consists of three strings, one representing a first name, one representing a middle initial, and the third representing a last name. Each time the client submits data, all of the data is retrieved from the table, formatted into an HTML table, and sent back to be displayed on the client screen.

The program was tested using three computers on the network, all running JDK 1.2 under Win95.

For this test, the HTTP server program from JavaSoft named Java Web Server 1.1.1 was running on one machine. The database server program named mSQL was running on a second machine. A browser was running on the third machine

An HTML form file named Servlet05.html containing four fields and one submit button was loaded into the browser. The four fields were:

The servlet was activated and data was sent to the server by the highlighted tags from the HTML file shown below. If you aren't familiar with the HTML syntax for creating and processing forms, you will need to look this up in an appropriate HTML book or tutorial. Many such documents are freely available on the web. (Note that this version of the HTML file makes use of the servletrunner program running on the localhost.)

<FORM METHOD=GET ACTION="http://localhost:8080/servlet/Servlet05">
Enter your name and press the button<P>
Clear Database, y or n: <INPUT TYPE=TEXT NAME="clear"><P>
First Name: <INPUT TYPE=TEXT NAME="firstName"><P>
Middle Initial: <INPUT TYPE=TEXT NAME="middleInitial"><P>
Last Name: <INPUT TYPE=TEXT NAME="lastName"><P>
<INPUT TYPE=SUBMIT>

The first line shown above caused a GET request to be sent to the server and caused the server to call the servlet named Servlet05. The request from the client was handled by an overridden doGet() method in the servlet.

The servlet got a connection to a specified database server on the machine named node3 using the following URL:

jdbc:msql://node3:1114/JunkDB

Note that the // is part of the URL and is not a comment indicator in this case.

Then, the code in the servlet obtained the value of the parameter named clear from the client. If the value was either Y or y, and a table named servlet05Table already existed, it was deleted.

Next, the servlet created a new table named servlet05Table if a table of that name didn't already exist

The servlet then got the values for the parameters named firstName, middleInitial, and lastName from the client and inserted them into the database table.

After this, the servlet

The program was also successfully tested using the JavaSoft servletrunner program running on localhost as a substitute for the HTTP server. In this case, only two different machines were involved: one for the browser and servletrunner and one for the database server..

Interesting Code Fragments

The first fragment is shown here simply to remind you of the requirement to import special packages dealing with servlets and JDBC.

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.sql.*;

The next fragment shows the beginning of the overridden doGet() method, and should remind you that each of the service() methods receive two objects as parameters. One object passes data from the client to the servlet. The other object passes data from the servlet to the client.

public class Servlet05 extends HttpServlet{
  public void doGet(HttpServletRequest req, 
                    HttpServletResponse res)
                      throws ServletException, IOException{

The next fragment declares two local reference variables that you will need to know about when they are used later.

    try {
      Statement stmt;//used for database access
      ResultSet resultSet;//used for database access

The next fragment contains code that you have seen in several earlier lessons.

As discussed in earlier lessons, it is necessary to set the content type of the data to be returned to the client before accessing the writer or stream that will be used to return that data. This is illustrated in the following fragment that sets the content type to the standard for HTML text.

The fragment gets an output writer object that will be used to send data back to the client. It also begins constructing the tags and other information that is required to construct an HTML page on the fly.

Finally, the fragment

      res.setContentType("text/html");

      PrintWriter out = res.getWriter();
      
      out.println("<HTML>");
      out.println("<HEAD><TITLE=Servlet05</TITLE></HEAD>");
      out.println("<BODY>");
      
      Class.forName("com.imaginary.sql.msql.MsqlDriver");

      String url = "jdbc:msql://node3:1114/JunkDB";

      Connection con = DriverManager.getConnection(url,
                                           "baldwin", ""); 

The next fragment also contains code that you have seen before. First, it gets a Statement object linked to the database connection to be used later for manipulating the database.

This fragment gets the incoming parameter named clear from the client. If the value of the parameter is either Y or y, an attempt is made to delete the existing database table. Note that clear is the name of a local reference variable in the method, and "clear" as a parameter to the getParameter() method is a string that matches the name of the data-input field on the HTML form. There is no requirement for these two names to match.

Then the servlet tries to create a new database table each time it is invoked. If a table with the same name already exists, the attempt to create a new table is aborted and an exception is thrown. The exception is essentially ignored.

Since you have seen code of this type before, I omitted the code from the exception handlers for brevity. You can view it in the complete listing of the program near the end of this lesson.

      stmt = con.createStatement();

      String clear = req.getParameter("clear");
      if(clear.toUpperCase().equals("Y")){//Delete table
        try{
          stmt.executeUpdate("DROP TABLE servlet05Table");
        }catch(Exception e){//code omitted for brevity}
      }//end if(clear.toUpperCase().equals("Y")

      try{
        stmt.executeUpdate("CREATE TABLE servlet05Table ("
                + "firstName char(15),"
                + "middleInitial char(2),"
                + "lastName char(15)"
                + ")");
      }catch(Exception e){//code omitted for brevity}

The next fragment also contains code that you have seen before. This fragment uses the getParameter() method of the incoming request object to obtain the values of the firstName, middleInitial, and lastName fields of the HTML form. Then this new data is inserted into (added to) the data already in the database table.

Pay particular attention to the requirement to surround the new data with single quotes to satisfy the SQL syntax requirement for string data.

The fragment also creates a Statement object and uses that object to execute a query on the database. The result of the query is encapsulated in an object of type ResultSet that is referred to by the reference variable named resultSet.

      String firstName = req.getParameter("firstName");
      String middleInitial = req.getParameter(
                                          "middleInitial");
      String lastName = req.getParameter("lastName");
        
      stmt.executeUpdate("INSERT INTO servlet05Table ("
                    + "firstName,middleInitial,lastName)"
                    + " VALUES('" 
                    + firstName + "','" 
                    + middleInitial + "','" 
                    + lastName + "'"
                    + ")");

      stmt = con.createStatement();

      resultSet = stmt.executeQuery(
                           "SELECT * from servlet05Table");

The objective of this servlet is to extract all of the data from the database without prior knowledge of the database structure, format the data into an HTML table, and send the table back to the client for display on the client screen.

The next fragment simply displays some text on the client screen.

      out.println("Contents of Database<BR><BR>");

Finally, the remaining fragments provide information that is new to this lesson. An object of type ResultSetMetaData can be used to represent a ResultSet object and provide information about that object. The next fragment gets a ResultSetMetaData object that represents the ResultSet object produced by the query in the previous fragment.

The getColumnCount() method of the ResultSetMetaData object is used to obtain the number of columns in the ResultSet object. This information will be used in the conditional clause of loop structures in subsequent code.

The ResultSetMetaData interface declares about twenty different methods that can be called to obtain various types of information about a ResultSet object. getColumnCount() is only one of them.

      ResultSetMetaData resultSetMetaData = 
                                   resultSet.getMetaData();
      int numColumns = resultSetMetaData.getColumnCount();

This code constructs a String object that contains the entire HTML table, including tags and data. The contents of the String object are then sent to the client. An object of the StringBuffer class is used to construct the String on the fly.

The next fragment begins the construction of the StringBuffer object. First, the object is instantiated. Then the append() method is used to append an HTML tag. The HTML tag specifies a table with a border two pixels in width. If you aren't familiar with the HTML tag syntax used to construct a table, you will need to look it up in another source.

The fragment also appends the HTML tag that begins the first row of data in the table.

      StringBuffer strBuf = new StringBuffer();

      strBuf.append("<TABLE BORDER=2>\n");
      strBuf.append("<TR>");//table row tag

The next fragment iterates on the number of columns in the table. During each iteration, it appends a table header tag followed by the string from the column label for that column in the ResultSet table. Thus, the column labels in the ResultSet table are used as column headers in the resulting HTML table.

After the loop is exhausted, a tag is appended indicating the end of the row.

      for(int i = 1; i <= numColumns; i++){
        strBuf.append("<TH>"//table header tag
                    + resultSetMetaData.getColumnLabel(i));
      }//end for loop
      strBuf.append("</TR>\n");//end table row

The next fragment is a little more complicated. This fragment contains an outer while loop that iterates once for each row of data in the ResultSet table. It also contains an inner nested for loop that iterates once for each column in the ResultSet table.

The behavior of the outer loop is simply to provide the HTML tags that begin and end successive rows in the HTML table.

During each iteration of the inner loop (one iteration for each column), a table data tag is appended to the StringBuffer object.

Then the code gets the object that represents the cell in the ResultSet table for that column and that row. The object is tested for null. If it is not null, the toString() method is used to convert it to a String which is appended to the StringBuffer object.

If it is null, an HTML non-breaking space is appended to the StringBuffer object. This effectively produces an empty cell in the resulting HTML table.

      while(resultSet.next()) {
        strBuf.append("<TR>");//new row

        for(int i = 1; i <= numColumns; i++){
          strBuf.append("<TD>");//table data element tag

          Object obj = resultSet.getObject(i);

          if(obj != null){
            strBuf.append(obj.toString());
          }//end if(obj != null)
          else{
            strBuf.append("&nbsp;");//non-breaking space
          }//end else

        }//end for loop

        strBuf.append("</TR>");//end table row
      }//end while loop

The next fragment appends the HTML tag to specify the end of the table and then sends the entire string representation of the StringBuffer object to the client. This causes the HTML table to be displayed on the client screen.

      strBuf.append("</TABLE>");//end table 
      out.println(strBuf);//send table to client

The next fragment constructs the remaining tags required by the HTML format and closes the connection to the database.

      out.println("</BODY></HTML>");

      con.close();

This is followed by some routine wrap-up code that is not shown here, but which you can view in the complete listing of the program in the next section.

Program Listing

A complete listing of the program follows.

/*File Servlet05.java, Copyright 1999, R.G.Baldwin
Rev 1/8/99

The primary purpose of this program is to illustrate the
use of the JDBC ResultSetMetaData interface, which provides
a way to get information about the underlying structure of
a ResultSet object at runtime.

This servlet performs a query on a database and then 
displays the resulting table in an HTML table without 
knowing anything about the contents of the ResultSet object
in advance.

This is an upgrade to the servlet named Servlet02.  The
upgrade is described by item 4 in the following list.

This servlet performs the following operations:
1. Use a servlet to get data from a client form.
2, Use the servlet to access a database to store
   data obtained from the client form.
3. Use the servlet to access the database to retrieve
   data.
4. Use the servlet to automatically format the data 
   into an HTML table and send it back to the client.

The program was tested using three computers on the 
network, all running JDK 1.2 under Win95.

For this test, the HTTP server program from JavaSoft 
named Java Web Server 1.1.1 was running on one machine.

The database server program named mSQL was running on a 
second machine.

A browser was running on the third machine

An HTML file containing four fields and one submit button
was loaded into the browser.  The four fields were:
  
clear (y or n)
firstName
middleInitial
lastName

The servlet was activated by the following tag in the
HTML file.

<FORM METHOD=GET 
ACTION="http://webserver:8080/servlet/Servlet05">

This tag caused a GET request to be sent to the server and
caused the server to call the servlet named Servlet05. The
request from the client was handled by an overridden
doGet() method in the servlet.

The servlet got a connection to a specified database
server on the machine named node3 using the following URL:
  
jdbc:msql://node3:1114/JunkDB

Then, the code in the servlet obtained the value of the
parameter named clear from the client.  If the value was
either Y or y, and a table named servlet05Table already
existed, it was deleted.

Next, the servlet created a new table named servlet05Table
if a table of that name didn't already exist

The servlet then got the values for the parameters named
firstName, middleInitial, and lastName from the client and 
inserted them into the database table.

After this, the servlet 
- Accessed the database server and got a ResultSet table
  containing all of the data in the database
- Used ResultSetMetaData to get information about the 
  ResultSet table
- Used that information to format the data in the ResultSet
  table into an HTML table and returned the HTML table to
  the browser where it appeared in the browser window.

The program was also successfully tested using the JavaSoft
servletrunner program running on localhost as a substitute
for the HTTP server.  In this case, only two different
machines were involved:  one for the browser and 
servletrunner and one for the database server.
**********************************************************/
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.sql.*;

public class Servlet05 extends HttpServlet{
  public void doGet(HttpServletRequest req, 
                    HttpServletResponse res)
                      throws ServletException, IOException{
                        
    try {
      Statement stmt;//used for database access
      ResultSet resultSet;//used for database access

      //Set the content type of the data to be sent back
      // to the client.
      res.setContentType("text/html");
      
      //Get an output writer object to send data back to
      // the client.
      PrintWriter out = res.getWriter();
      
      //Begin constructing the HTML page to send back to
      // the client.
      out.println("<HTML>");
      out.println("<HEAD><TITLE=Servlet05</TITLE></HEAD>");
      out.println("<BODY>");
      
      //Register the JDBC driver
      Class.forName("com.imaginary.sql.msql.MsqlDriver");
      //Construct URL for database on node3
      String url = "jdbc:msql://node3:1114/JunkDB";
      //Get a connection to the database
      Connection con = DriverManager.getConnection(url,
                                           "baldwin", ""); 
      //Get a Statement object
      stmt = con.createStatement();

      //Get the incoming parameter named clear from the 
      // client. If it is Y or y, delete the existing
      // table.
      String clear = req.getParameter("clear");
      if(clear.toUpperCase().equals("Y")){//Delete table
        try{
          stmt.executeUpdate("DROP TABLE servlet05Table");
        }catch(Exception e){
          out.println(e + "<br>");
          out.println("Unable to delete table<br><br>");
        }//end catch
      }//end if(clear.toUpperCase().equals("Y")
      
      //Create a new table named servlet05Table in the 
      // database if it doesn't already exist
      try{
        stmt.executeUpdate("CREATE TABLE servlet05Table ("
                + "firstName char(15),"
                + "middleInitial char(2),"
                + "lastName char(15)"
                + ")");
      }catch(Exception e){
        out.println(e + "<br>");
        out.println("Unable to create table<br><br>");
      }//end catch
      
      //Get the incoming data parameters from the client
      String firstName = req.getParameter("firstName");
      String middleInitial = req.getParameter(
                                          "middleInitial");
      String lastName = req.getParameter("lastName");
        
      //Put the data into the database table. Don't forget
      // to enclose the string data in single quotes to 
      // satisfy the SQL syntax requirement.
      stmt.executeUpdate("INSERT INTO servlet05Table ("
                    + "firstName,middleInitial,lastName)"
                    + " VALUES('" 
                    + firstName + "','" 
                    + middleInitial + "','" 
                    + lastName + "'"
                    + ")");
      
      //Create a statement object linked to the database
      // connection.
      stmt = con.createStatement();

      //Use the statement object to execute a query on the
      // database.
      resultSet = stmt.executeQuery(
         "SELECT * from servlet05Table");

      out.println("Contents of Database<BR><BR>");

      //Get info about the ResultSet
      ResultSetMetaData resultSetMetaData = 
                                   resultSet.getMetaData();
      int numColumns = resultSetMetaData.getColumnCount();
      
      //Start an output string that is an HTML table
      StringBuffer strBuf = new StringBuffer();
      //begin HTML table
      strBuf.append("<TABLE BORDER=2>\n");
      
      //Use column labels from ResultSet for table titles
      strBuf.append("<TR>");//table row tag
      for(int i = 1; i <= numColumns; i++){
        strBuf.append("<TH>"//table header tag
                    + resultSetMetaData.getColumnLabel(i));
      }//end for loop
      strBuf.append("</TR>\n");//end table row
      
      while(resultSet.next()) {
        strBuf.append("<TR>");//new row
        for(int i = 1; i <= numColumns; i++){
          strBuf.append("<TD>");//table data element tag
          Object obj = resultSet.getObject(i);
          if(obj != null){
            strBuf.append(obj.toString());
          }//end if(obj != null)
          else{
            strBuf.append("&nbsp;");//non-breaking space
          }//end else
        }//end for loop
        strBuf.append("</TR>");//end table row
      }//end while loop
      strBuf.append("</TABLE>");//end table
      
      out.println(strBuf);//send table to client

      //Finish the construction of the html page
      out.println("</BODY></HTML>");

      //Enable the following statement to delete the table
      // when the servlet terminates.
      //stmt.executeUpdate("DROP TABLE servlet05Table");
      
      //Close the connection to the database      
      con.close();
    }catch( Exception e ) {
      e.printStackTrace();
    }//end catch
  }//end doGet()
}//end class Servlet05

-end-