Skip to main content.

Web Based Programming Tutorials

Homepage | Forum - Join the forum to discuss anything related to programming! | Programming Resources

Tricks of the Java Programming Gurus

Chapter 18 -- Developing Database Applications and Applets

Chapter 18

Developing Database Applications and Applets


CONTENTS


Business users have long benefited from the ability to store data in a central location for access by many different users. By connecting the millions of once-isolated home computers on a common network, the Internet has empowered developers to bring the advantages of client/server computing into the home. Until Java, such Web applications have primarily used CGI for connecting to databases.

For a few years now, the business solution to client/server development has been rapid application development tools such as Borland Delphi, Sybase PowerBuilder, and Microsoft Visual Basic. These applications provide both rapid GUI development through the use of drag-and-drop screen painting as well as a library of tools for accessing data housed in relational databases. Unfortunately, these applications are not well suited to Internet development, where platform independence, security, and distribution are all imperative.

A major strength of rapid application development products is their database connectivity. The Java specification as well as the original release of the JDK made no provisions for Java database access. In March 1996, Sun began to address this need with the draft release of the Java Database Connectivity specification, JDBC. This chapter addresses the problem of database access in Java and demonstrates how to write code that conforms to and enhances the JDBC interface.

Storing Data for the Web

Simple applets rarely need to perform database access. They are generally executed as on-off programs without the need to save any state information across executions. As Java developers move their work out of the realm of the simple applet, they can find a need to access some sort of data store. A popular, yet simple, example is the ubiquitous page counter. A page counter is simply an applet or CGI script that keeps track of how many times a particular page has been hit and displays that number on the page (see Figure 18.1).

Figure 18.1 : A Web page running a counter applet.

At the other extreme in complexity are the search engines that enable you to perform keyword searches to find the most trivially related pages of information existing on the Internet. No matter how complex the application, the basic data management needs are the same. Many users need to gain access to the same piece of information requiring an application be built in such a way that it can access and/or modify centrally stored data. The developer must then take the following steps to provide users with access to data:

Providing Access to Data

Whether Java or some other language is used to build these pieces, the DBMS being used will have a direct impact on the implementation. A detailed discussion of database management systems is well beyond the scope of this book. When choosing among the various technologies, however, it is important to keep your needs (and your wallet) in mind while resisting the dazzle of technology. Three basic data storage technologies exist that serve various needs:

With the advent of the high multimedia content data storage needs of the Internet, developers have been more open to the idea of using object databases. In addition to being better suited to the unusual demands of storing multimedia data, object databases also help provide a true object paradigm all the way across from data store to client application.

Accessing a pure object database with any front-end tool is a challenge. Because the JDBC specification revolves around ANSI SQL-2 compliance and few object databases have SQL support, accessing an object database through Java will prove to be doubly challenging.

For developers not faced with the need to store complex data, any traditional relational databases should do exactly what is needed. The grand trick in doing Java programming with a relational database, or doing any object programming against a relational database, is the mapping between the dynamic realm of objects and the static realm of pure data.

Paving the road between these two seemingly disparate technologies are the object-relational databases. For developers with complex data modeling needs, an object-relational database can provide the object modeling power of an object database, while maintaining the ease of data access afforded by traditional relational systems.

The JDBC API

In order to provide a common base API for accessing data, Sun, with support from a number of independent software vendors, developed JDBC. JDBC defines a number of Java interfaces to enable developers to access data independent of the actual database product being used to store the data. In theory, an application written against the basic JDBC API using only SQL-2 can function against any database technology that supports SQL-2.

Database Requirements

Data may be stored in a wide variety of formats using various technologies. In addition to the three major modern database management systems, there are other things to consider, such as hierarchical databases and file systems. Any low-level API even trying to find a least common denominator among these data storage methods would end up with the null set. JDBC, however, mandates no specific requirements on the underlying DBMS. Rather than dictating what sort of DBMS an application must have in order to support JDBC, the specification places all its requirements on the JDBC implementation.

The JDBC specification primarily mandates that a JDBC implementation support at least ANSI SQL-2 Entry Level. Because most common RDBMS and OORDBMS systems support SQL-2, this requirement provides a reasonable baseline from which to build database access. In addition, because SQL-2 is required only at the JDBC implementation level, that implementation can provide its own SQL-2 wrapper around non-SQL data stores. Writing such a wrapper, though, would likely prove to be a huge task.

The JDBC Interfaces

JDBC defines eight interfaces that must be implemented in order to be JDBC-compliant:

Figure 18.2 shows these interfaces and how they interact in the full JDBC object model.

Figure 18.2 : The JDBC object model.

The central object around which the whole concept revolves is the java.sql.DriverManager object. It is responsible for keeping track of the various JDBC implementations that may exist for an application. If, for example, a system were aware of Sybase and Oracle JDBC implementations, the DriverManager would be responsible for tracking those implementations. Any time an application desires to connect to a database, it asks the DriverManager to give it a database connection, using a database URL through the DriverManager.getConnection() method. Based on this URL, the DriverManager searches for a Driver implementation that accepts the URL. It then gets a Connection implementation from that Driver and returns it to the application.

Note
What is a database URL? In order to enable an application to specify the database to which it wants to connect, JDBC uses the Internet standard Universal Resource Locator system. A JDBC URL c onsists of the following pieces: jdbc:<subprotocol>:<subname>. As with URLs you have seen all over the Internet, the first element is the resource protocol-in this case, a JDBC data source. The subprotocol is specific to the JDBC implementation. In many cases, it will likely be the DBMS name and version, for example syb10 might indicate Sybase System 10. The subname element is any information specific to the DBMS that tells it where it needs to connect. For mSQL, the JDBC URL is in the format of jdbc:msql://hostname:port/database. JDBC itself does not much care what a database URL looks like. The important thing is simply that a desired JDBC implementation can recognize the URL and get the information it needs to connect to a database from that URL.

The DriverManager is the only instantiated class provided by JDBC other than exception objects and a few specialized subclasses of java.util.Date. Additional calls made by an application are written against the JDBC interfaces that are implemented for specific DBMSs.

java.sql.Driver

A Driver is essentially a Connection factory. The DriverManager uses it to determine whether it can handle a given URL. If it can handle the URL, it should create a Connection object and return it to the DriverManager. Because an application only indirectly references a Driver through the DriverManager, applications are rarely concerned with this class.

java.sql.Connection

A Connection is a single database session. As such, it stores state information about the database session it manages and provides the application with Statement, PreparedStatement, or CallableStatement objects for making calls during the session.

java.sql.Statement

A Statement is an unbound SQL call to the database. It is generally a simple UPDATE, DELETE, INSERT, or SELECT where no columns need to be bound to Java data. It provides methods for making such calls and returns to the application the results of any SELECT statements or the number of rows affected by an UPDATE, DELETE, or INSERT.

Statement has the subclass PreparedStatement, which is in turn subclassed by CallableStatement. A PreparedStatement is a precompiled database call that requires parameters to be bound. An example of a PreparedStatement might be a stored procedure call that has no OUT or INOUT parameters. For stored procedures with OUT or INOUT parameters, an application should use the CallableStatement interface.

java.sql.ResultSet

An application gets data returned by a SELECT query through the implementer of this interface. Specifically, the ResultSet object enables an application to retrieve sequentially rows of data returned from a prior SELECT call. It provides a multitude of methods that enable you to retrieve a given row as any data type to which it makes sense to convert it. For example, if you have a date stored in the database as a datetime, you can retrieve it through the getString() method to use it as a String.

The Meta-Data Interfaces

Meta-data is data about data. Specifically, it is a set of data that gives you information on the database and data retrieved from the database. Java provides two meta-data interfaces, java.sql.ResultSetMetaData and java.sql.DatabaseMetaData. The ResultSetMetaData provides a means for getting information about a particular ResultSet. For example, among other things, it provides information on the number of columns in the result set, the name of a column, and its type. The DatabaseMetaData interface, on the other hand, gives the application information on the database in general, such as what levels of support it has, its name, version, and other bits.

Simple Database Access Using the JDBC Interfaces

An application for which database independence is paramount should be written to the JDBC specification, using no database specific calls and making use of no SQL that is not part of the ANSI SQL-2 standard. In such code, no reference should be made to a specific implementation of JDBC. Writing a simple database application using only JDBC calls involves the following steps:

  1. Ask the DriverManager for a Connection implementation.
  2. Ask the Connection for a Statement or subclass of Statement to execute your SQL.
  3. For subclasses of Statement, bind any parameters to be passed to the prepared statement.
  4. Execute the statement.
  5. For queries, process the result set returned from the query. Do this for each result set (if you have multiple result sets) until there are none left.
  6. For other statements, check the return value for number of rows affected.
  7. Close the statement.
  8. Process any number of such statements and then close the connection.

The Counter Applet Example

The counter applet discussed earlier in this chapter provides a simple example of JDBC programming. Using the JDBC interfaces, this applet connects to a database, determines how many times the page on which it appears has been hit, updates the page to reflect the new hit, and finally displays the number of hits. In order to use this example, you need a database engine to run your database and a JDBC driver to access that database engine. If you do not have a database engine, download mSQL and JDBC, which are both free for noncommercial use. Links to mSQL and the JDBC class may be found through http://www.imaginary.com/Java/. In addition, you need to create a table called t_counter with the fields counter_file (chAR(100), PRIMARY KEY) and counter_num (INT, NOT NULL). The following mSQL script creates the table:

DROP TABLE t_counter\p\g

CREATE TABLE t_counter(
        counter_file    chAR(100)    PRIMARY KEY,
        counter_num     INT          NOT NULL
)\p\g

The applet consists of two classes, Counter and Database. The Counter class is the subclass of applet that provides the user interface to the applet. It contains two instance variables. One, count, is the number this applet is supposed to display, the number of page hits. The other, database, is an instance of the Database class that provides wrappers for the JDBC access needed by the applet.

Counter does not define any new methods; rather, it simply overrides the java.applet.Applet.init() and java.applet.Applet.paint() methods. The init() method is used to create a Database instance and find out from it what the page hit count is for display. The paint() method displays the page hit count.

The interesting JDBC-related work is all encapsulated inside the Database class. It has a single instance variable, connection, which is an instance of a JDBC Connection implementation. The connection variable is initialized in the Database class constructor:

public Database(String url, String user, String pass)
 throws java.sql.SQLException  {
    connection =
        DriverManager.getConnection(url, user, pass);
}

By getting an instantiated Connection object, the applet is ready to do whatever database access it needs to do.

Tip
As of the printing of this book, the java.sql package has not been incorporated into Java browsers such as Netscape. Due to a security feature of such browsers, which prevents the loading of classes in the java.* namespace, the applet examples in this chapter will not work properly. So how do I know they work at all? To get an applet using the java.sql classes to work, simply rename your java.sql packages to something else and recompile them. That moves them from the java.* namespace so that such browsers can load them. This problem does not affect standalone applications, and it will not apply once the JDBC specification is finalized and java.sql classes are incorporated into the browser releases.

The applet uses the getCount() method to figure out how many page hits this particular access to the Web page represents. That seemingly benign query actually represents several steps:

  1. Create a Statement object.
  2. Formulate and execute the SELECT query.
  3. Process the result.
  4. Increment the hit count.
  5. Format and execute an UPDATE or INSERT statement.
  6. Close the Statement and Connection objects.

Creating the Statement is done through the JDBC call:

java.sql.Statement statement = connection.createStatement();

For this query, you want the number of hits for this page from the t_counter table:

sql = "SELECT counter_num FROM t_counter " +
      "WHERE counter_file = '" + page + "'";
result_set = statement.executeQuery(sql);

The result_set variable now holds the results of the query. For queries that return multiple rows, an application would loop through the next() method in the result set until no more rows existed. This query, however, should only be returning one row with one column, unless the page has never been hit. If the page has never been hit, the query will not find any rows and the count variable should be set to 0:

if( !result_set.next() ) count = 0;

Otherwise, you need to retrieve that row into the count variable as an integer:

else count = result_set.getInt(1);

After incrementing the count to reflect this new hit, you close out the Statement object and get a new one in order to prepare for the UPDATE:

count++;
statement.close();
statement = connection.createStatement();

If this is the first time the page is being hit, the applet needs to INSERT a new row into the database. Otherwise, it should UPDATE the existing row:

if( count == 1 ) {
    sql = "INSERT INTO t_counter " +
           "(counter_file, counter_num) " +
            "VALUES ('" + file + "', " + count + ")";
}
else {
    sql = "UPDATE t_counter " +
          "SET counter_num = " + count + " " +
           "WHERE counter_file = '" + file + "'";
}
statement.executeUpdate(sql);

The method then cleans up and returns the hit count. Listing 18.1 puts the whole applet together.


Listing 18.1. The Counter applet.
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;

public class Counter extends java.applet.Applet {
    Database db;
    String count;

    public void init() {
        String driver = getParameter("driver");
        String url = getParameter("url");
        String user = getParameter("user");
        String pass = getParameter("password");
        String page = getParameter("page");

        try {
            Class.forName(driver).newInstance();
            db = new Database(url, user, pass);
            count = db.getCount(page);
        }
        catch( java.sql.SQLException e ) {
            e.printStackTrace();
            count = "Database exception";
        }
        catch( Exception e ) {
            e.printStackTrace();
            count = "Unable to load driver";
        }
    }

    public void paint(java.awt.Graphics g) {
        g.setFont(new java.awt.Font(getParameter("font"),
                                    java.awt.Font.BOLD, 14));
        g.drawString(count, 5, 15);
    }
}

class Database {
    private Connection connection;

    public Database(String url, String user, String pass)
    throws java.sql.SQLException {
        connection =
            DriverManager.getConnection(url, user, pass);
    }

public String getCount(String page) {
        int count = 0;

        try {
            java.sql.Statement statement =
                connection.createStatement();
            java.sql.ResultSet result_set;
            String sql;

            sql = "SELECT counter_num FROM t_counter " +
                "WHERE counter_file = '" + page + "'";
            result_set = statement.executeQuery(sql);
            if( !result_set.next() ) count = 0;
            else count = result_set.getInt(1);
            count++;
            statement.close();
            statement = connection.createStatement();
            if( count == 1 ) {
                sql = "INSERT INTO t_counter " +
                    "(counter_file, counter_num) " +
                    "VALUES ('" + page + "', " +count+ ")";
            }
            else {
                sql = "UPDATE t_counter " +
                    "SET counter_num = " + count + " " +
                    "WHERE counter_file = '" + page + "'";
            }
            statement.executeUpdate(sql);
            statement.close();
            connection.close();
        }
        catch( java.sql.SQLException e ) {
            e.printStackTrace();
        }
        return ("" + count);
    }
}

Note
How do drivers get registered with the DriverManager? In the previous example, it was done by specifically loading the driver passed into the program through the driver parameter. A JDBC-compliant driver must tell the DriverManager about its existence when it gets instantiated. The preferred method of listing multiple JDBC drivers for the DriverManager is through the jdbc.drivers property.

Result Sets and the Meta-Data Interfaces

In simple applications such as the counter applet, there is no need to perform any tricks with the results from a query. The data is simply retrieved sequentially and processed. More commonly, however, an application will need to process the data in a more complex fashion. For example, a set of classes might want to deal with data on a more abstract level than the Database class from the counter example. Instead, such classes might not know exactly what data is being retrieved. They can query the meta-data interfaces to intelligently process such data that they would otherwise not know. Listing 18.2 shows a generic database view class that gets populated with database objects based on a result set.


Listing 18.2. A generic database view class.
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.util.Hashtable;
import java.util.Vector;

public class View {
    private Vector objects;

    public void populate(ResultSet result_set, String cl) {
        ResultSetMetaData meta_data;
        int i, maxi;

        try {
            objects = new Vector();
            meta_data = result_set.getMetaData();
            maxi = meta_data.getColumnCount();
            while( result_set.next() ) {
                Hashtable row = new Hashtable();
                DataObject obj;

                for(i=1; i<=maxi; i++) {
                    String key;
                    Object value;
                    int t;

                    key = meta_data.getColumnLabel(i);
                    t = meta_data.getColumnType(i);
                    value = result_set.getObject(i, t);
                    row.put(key, value);
                }
                obj = (DataObject)Class.forName(cl);
                obj.restore(row);
                objects.addElement(obj);
          }
        }
        catch( java.sql.SQLException e ) {
            e.printStackTrace();
            objects = new Vector();
            return;
        }
    }
}

In the View class, reference is made to a DataObject class that implements a restore(java.util.Hashtable) method not listed.

Because this is a generic class to be reused by many applications, it knows nothing about the queries it is executing. Instead, it takes any random result set and assumes that each row corresponds to an instance of the class named by the second parameter to populate().

In order to get the information it needs for performing the data retrievals, the populate() method first gets the meta-data object for this result set. This method is specifically interested in knowing how many columns are in the result set as well as the names of the columns. In order to store the columns in a Hashtable object that the DataObject object can use for restoring itself, all data must be in the form of objects. Thus, for each column in the result set, it finds its data type from the meta-data and retrieves the column as an object. The final step is to store it in the Hashtable.

Other JDBC Functionality

JDBC provides a lot of functionality beyond the commonly used methods already discussed:

Transaction Management

JDBC implementations should default automatically to committing transactions unless the application otherwise requests that transactions require an explicit commit. An application may toggle the automatic commit of the JDBC implementation it is using through the Connection.setAutoCommit() method. Here is an example:

connection.setAutoCommit(false);

Of course, by not setting the AutoCommit attribute or by setting it to true, the JDBC implementation will make certain that the DBMS commits after each statement you send to the database. When set to false, however, the JDBC implementation requires specific commits from the application before a transaction is committed to the database. A series of statements executed as a single transaction would look like this:

public void add_comment(String comment) {
    try {
        Statement s;
        ResultSet r;
        int comment_id;

        connection.setAutoCommit(false);
        s = connection.createStatement();
        r = s.executeQuery("SELECT next_id " +
                          "FROM t_id " +
                          "WHERE id_name = 'comment_id'");
        if( !r.next() ) {
             throw new SQLException("No comment id exists " +
                                     "in t_id table.");
        }
        comment_id = r.getInt(1) + 1;
        s.close();
        s = connection.createStatement();
        s.executeUpdate("UPDATE t_id " +
                        "SET comment_id = " + comment_id + " " +
                        "WHERE next_id = 'comment_id'");
        s.close();
        s = connection.createStatement();
        s.executeUpdate("INSERT INTO t_comment " +
                         "(comment_id, comment_text) " +
                         "VALUES(" + comment_id + ", '" +
                          comment + "')");
        connection.commit();
    }
    catch( SQLException e ) {
        e.printStackTrace();
        try {
            connection.rollback();
        }
        catch( SQLException e2 ) System.exit(-1);
    }
}

This method is used to add a comment to a comment table for some applications. In order to insert the new comment, it needs to generate a new comment_id and then update the table for generating id's so that the next one will be one greater than this one. Once it has an id for this comment, it then inserts the comment into the database and commits the entire transaction. If an error occurs at any time, the entire transaction is rolled back.

JDBC currently has no support for a two-phase commit. Applications written against distributed databases require extra support to allow for a two-phase commit.

Cursor Support

JDBC provides limited cursor support. It enables an application to get a cursor associated with a result set through the ResultSet.getCursorName() method. The application can then use the cursor name to perform positioned UPDATE or DELETE statements.

Stored Procedures

Stored procedures are precompiled SQL statements stored in the database that enable faster execution of SQL. JDBC supports stored procedures through the CallableStatement class. In the counter applet, a stored procedure could have been used to update the page hit count in the following way:

CallableStatement s = connection.prepareCall("{call sp_upd_hit_count[?, ?]}");
s.setStringParameter(1, "file");
s.setIntParameter(2, count);
s.executeUpdate();

Multiple Result Sets

In some cases, especially with stored procedures, an application can find a statement returning multiple result sets. JDBC handles this through the method Statement.getMoreResults(). Although there are result sets left to be processed, this method returns true. The application can then get the next ResultSet object by calling Statement.getResultSet(). Processing multiple result sets simply involves looping through as long as Statement.getMoreResults() returns true.

Building a JDBC Implementation

Building a JDBC implementation requires a lot more in-depth knowledge of both your DBMS and the JDBC specification than does simply coding to it. Most people will never encounter the need to roll their own implementation, because database vendors will logically want to make them available for their product. Understanding the inner workings of JDBC, however, can help advance your application programming.

JDBC is a low-level interface. It provides direct SQL-level access to the database. Most business applications and class libraries will want to abstract from that SQL-level access to provide such things as object persistence and business-aware database access. A narrow example of such an abstraction is the Database class from the counter example.

The ideal object method of accomplishing these goals is to reuse existing JDBC implementations for the DBMS in question and add custom interfaces on top of those. If the DBMS is an oddball DBMS, or perhaps if concerns about the available implementations exist, writing one from scratch makes sense.

Implementing the Interfaces

The first concern of any JDBC implementation is how it is going to talk to the database. Figure 18.3 illustrates the architecture of three possible JDBC implementations. Depending on the design goals in question, one of these methods will suit any JDBC implementation:

Figure 18.3 : Possible JDBC implementation architectures.

Extending a vendor JDBC implementation, of course, is not really the same as building a JDBC implementation. Because a key to any object-oriented project is reusing code instead of building from scratch, however, it is listed here.

With all three architectures, the application is apparently isolated from the actual communication mechanism. In truth, however, the native C library method places severe restrictions on any application using a JDBC implementation built on top of it. Because it uses native calls, it is naturally not portable across operating systems. In addition, due to virtual machine restrictions on most browsers, native calls are either fully restricted or severely limited.

Using one of these mechanisms for database communication, you need to construct the four basic interfaces: java.sql.Driver, java.sql.Connection, java.sql.Statement, and java.sql.ResultSet. These will provide minimum functionality so that testing against simple queries and updates can be done. Once these are functional, the implementation needs the meta-data interfaces as well as the Statement subclasses in order to be complete and JDBC-compliant.

Extending JDBC

Nothing requires an application to use the JDBC interface to access a database. In fact, before JDBC, developers were programming to Java classes written specifically to go against several major database engines. JDBC isolates the database access behind a single interface. This isolation can provide developers with the ability to write database access in Java without having to know which database engine their application is actually hitting. With a single prevalent database API, finding people with experience programming against it proves much simpler than finding people to program against a proprietary API. JDBC is, however, a low-level specification that requires developers to write both SQL code as well as Java code.

Both examples in this chapter demonstrate two different ways in which you can extend JDBC. In the counter applet, a database class was created as a wrapper around the JDBC implementation. The applet itself was divided into a representational portion, the Counter class, and a functional portion, the Database class. If changes are made to the visual representation, such as making the hit count appear through an odometer graphic, no changes will need to be made to the functional logic, because it is isolated in a separate class. In fact, if the applet were more complex, requiring multiple developers, all the SQL is still isolated in a class specifically interested in the functional behavior of the application. This reduces the amount of people needing to write SQL code.

The View class example was a more abstract way of extending JDBC. The View class assumes that rows in result sets translate into business objects. In an application using this class, View objects are created whose purpose is to make JDBC calls and populate the applications with meaningful objects.

Another manner in which JDBC can be extended is to take advantage of database-specific features. Although it is prudent to question the need to make use of any proprietary features of a given DBMS, it is equally important that you do not ignore the extra power a specific DBMS gives you. It is, after all, very rare that an application actually needs to switch database engines.

Designing a Database Application

Knowing the JDBC API and coding cute applets are naturally just the start to database programming in Java. In order to harness the advantages of Java, application designers need to be able to address the design issues raised by Java. The entire Java paradigm empowers developers to write database applications and applets using architectures that before were either very complex or simply not supported by other tools. Two such buzzwords that have been flying around the client/server world for a while are distributed objects and three-tier client/server.

Security Issues

Before going off the edge into the deep end, Java does put some restrictions on applets for security reasons which can appear to be particularly limiting to the database developer. The following are two particular applet restrictions that affect database programmers:

The native call limitation affects programmers who need to use some sort of C- or operating system-level library in order to design an applet. This is especially troublesome to applet writers who need to take advantage of a database-specific feature not supported outside of native calls.

To veteran client/server developers, however, the most troubling idea is likely that your Web server must be on the same machine your applet is connecting to for database access. Specifically, most Java virtual machines restrict applets from connecting to any machine except the host that served the applet. The applet therefore cannot connect directly to any local or third-machine databases. As limiting as this particular restriction seems, a three-tier architecture provides a liberating solution.

Constructing a Three-Tier Application

Two-tier applications tend to push a lot of processing onto the client machines. This architecture poses several problems:

With the guaranteed execution environment of the Java virtual machine and an easy-to-use Internet socket interface, Java is actually well-suited to the implementation of three-tier systems. A three-tier application is one where a third application layer exists between the client and server layers of traditional two-tier client/server development. This middle layer has a wide variety of uses depending on the application in question.

The three-tier architecture uses the middle layer to separate business processing from the visual representation of data. This layer, called the application server, is responsible for knowing how to find and manipulate business data. The client evolves into a much leaner application responsible only for retrieving information from the application server and displaying it on the screen.

In addition to removing a huge processing burden from client machines, this application server can be used to consolidate enterprise-wide business rules. Where business rules had to be rewritten for each two-tier application thrust upon the desktop, application servers process business rules in a single place for use by multiple applications. When the business rules change, a change to the application server takes care of that change for all the applications being run by the business.

Of specific interest to Java developers is the ability to hide any knowledge of the database server from the client. Because Internet clients view the applet or application as interfacing with a single application server, you can use that application server to determine such things as where the data really exists. Additionally, this back-end independence enables applications to scale much easier across CPUs. Figure 18.4 shows a three-tier architecture.

Figure 18.4 : A three-tier Java applet or application.

A Three-Tier Bug Tracking System

The application server forms the core of a three-tier architecture. In it, the business rules are defined and processed. Implementing the counter using a three-tier architecture would naturally be massive overkill. Instead, the ideal application for a three-tier design is one where some manipulation of data occurs, or where the data can be viewed in multiple fashions (or even better, by multiple applications). The first step in building an application server would thus be to identify the data processing needs of the application.

Implementing a Three-Tier Application with Java

Figure 18.5 shows a bug tracking application implemented as a three-tier Java application.

Figure 18.5 : A bug tracking system using a three-tier architecture.

The only processing done on the client is the painting of GUI widgets and user data entry. On the other end, the database server is running on a machine otherwise inaccessible to the client applet. Bridging this gap is the application server that finds desired data, maps it from its relational state into objects, and performs operations on those objects.

With any three-tier architecture, one of the greatest programming challenges is getting the three layers to communicate with one another. JDBC or some similar set of database access classes should handle the application server-to-database server communication in a manner transparent to the application developer. The client-to-application server solution is still left wanting.

The two best methods for providing such communication in Java are Java sockets or distributed objects. Compared to sockets from other languages, Java sockets are quite simple to use. Sockets, however, force the developer to make esoteric decisions as to exactly what it is that is being communicated between client and application server, because method calls and object passing are better handled by the distributed objects solution. A socket solution generally best fits an application where the scope of communication is limited and well defined. The bug tracking system would be best implemented in this manner.

Distributed objects provide the more elegant solution. From the developer's point of view, the application server objects appear to be part of the same application as the client, just residing on a central server and available to other applications simultaneously. The developer handles communication simply through method calls.

Summary

Although the original Java release did not address the issue of database access, the JDBC specification attempts to address this issue by defining a set of interfaces that can give applications access to data independent of the DBMS being used to store that data. Though this back-end independence can be very liberating, it is important to balance it with the advantages of the DBMS being used.

Many books exist that cover only the subjects of database application design and programming. This chapter does not attempt to delve into those matters; instead, it focuses on the application of Java to database programming. Programmers interested in using Java to write database applications should become familiar with the general subject matter.

In spite of the vastness of the subject matter, this chapter should whet your appetite for database programming and prepare you at least enough to write simple applets and applications. Much of the Java experience you already have translates into many of the issues specific to Java database programming. For example, applets written to use a database must work around the strict security limitations of Java virtual machines. Using the basics of a three-tier architecture can help an applet get around this limitation while giving it greater functionality. It is, however, important not to overdesign a simple applet just for the sake of doing a three-tier design.