BUY THIS BOOK

Safari Books Online

What is this?

Looking to Reprint this content?


Java Enterprise in a Nutshell
Java Enterprise in a Nutshell A Desktop Quick Reference By Kris Magnusson, David Flanagan, Jim Farley, William Crawford
September 1999
Pages: 660

Cover | Table of Contents | Colophon


Table of Contents

Chapter 1: Introduction
This book is an introduction to, and quick reference for, the Java Enterprise APIs. Some of these APIs are a core part of the Java platform, while others are standard extensions to the platform. Together, however, they enable Java programs to use and interact with a suite of distributed network services that are commonly used in enterprise computing.
Just before this book went to press, Sun announced a new Java platform for enterprise computing. Java 2 Platform, Enterprise Edition, or J2EE, is the standard Java 2 platform with a number of extensions for enterprise computing. As of this writing, J2EE is still in its alpha stages; it will be some time before a complete specification and implementation are delivered. From the preliminary specifications, however, it appears that most of the enterprise-computing technologies that will be part of J2EE are already documented in this book. In the months ahead, you will undoubtedly hear quite a bit about Java 2 Platform, Enterprise Edition. Although you won't find that name used explicitly here, you can rest assured that this book documents the building blocks of J2EE.
Before we go any further, let's be clear. The term enterprise computing is simply a synonym for distributed computing: computation done by groups of programs interacting over a network.
Anyone can write distributed applications: you don't have to work for a major corporation, university, government agency, or any other kind of large-scale "enterprise" to program with the Java Enterprise APIs. Small businesses may not have the same enterprise-scale distributed computing needs large organizations have, but most still engage in plenty of distributed computing. With the explosive growth of the Internet and of network services, just about anyone can find a reason to write distributed applications. One such reason is that it is fun. When distributed computing is used to leverage the power of the network, the results can be amazingly cool!
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Enterprise Computing Defined
Before we go any further, let's be clear. The term enterprise computing is simply a synonym for distributed computing: computation done by groups of programs interacting over a network.
Anyone can write distributed applications: you don't have to work for a major corporation, university, government agency, or any other kind of large-scale "enterprise" to program with the Java Enterprise APIs. Small businesses may not have the same enterprise-scale distributed computing needs large organizations have, but most still engage in plenty of distributed computing. With the explosive growth of the Internet and of network services, just about anyone can find a reason to write distributed applications. One such reason is that it is fun. When distributed computing is used to leverage the power of the network, the results can be amazingly cool!
So, if the Java Enterprise APIs aren't used exclusively by enterprises, why aren't they called the Java Distributed Computing APIs? The reasons are simple. First, enterprise is a hot buzzword these days—everyone in the networking industry wants to be doing enterprise something. Second, large enterprises have lots of money to spend on costly hardware for running their expensive network server software. Since the enterprise is where the money is, we get the word enterprise in the APIs.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Enterprise Computing Demystified
Enterprise computing has a reputation for complexity and, for the uninitiated, it is often surrounded by a shroud of mystery. Here are some reasons enterprise computing can seem intimidating:
  • Enterprise computing usually takes place in a heterogeneous network: one in which the computers range from large mainframes and supercomputers down to PCs (including both top-of-the-line Pentium IIIs and outdated 386s). The computers were purchased at different times from a variety of different vendors and run two or three or more different operating systems. The only common denominator is that all the computers in the network speak the same fundamental network protocol (usually TCP/IP).
  • A variety of server applications run on top of the heterogeneous network hardware. An enterprise might have database software from three different companies, each of which defines different, incompatible extensions.
  • Enterprise computing involves the use of many different network protocols and standards. Some standards overlap in small or significant ways. Many have been extended in various vendor-specific, nonstandard ways. Some are quite old and use a vocabulary and terminology that dates back to an earlier era of computing. This creates a confusing alphabet soup of acronyms.
  • Enterprise computing has only recently emerged as an integrated discipline of its own. Although enterprise development models are today becoming more cohesive and encompassing, many enterprises are still left with lots of "legacy systems" that are aggregated in an ad-hoc way.
  • Enterprise programmers, like many of us in the high-tech world, tend to make their work seem more complicated that it actually is. This is a natural human tendency—to be part of the "in" group and keep outsiders out—but this tendency seems somehow magnified within the computer industry.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
The Java Enterprise APIs
The Java Enterprise APIs provide support for a number of the most commonly used distributed computing technologies and network services. These APIs are described in the sections that follow. The APIs are building blocks for distributed applications. At the end of the chapter, I'll present some enterprise computing scenarios that illustrate how these separate APIs can be used together to produce an enterprise application.
JDBC (Java Database Connectivity) is the Java Enterprise API for working with relational database systems. JDBC allows a Java program to send SQL query and update statements to a database server and to retrieve and iterate through query results returned by the server. JDBC also allows you to get meta-information about the database and its tables from the database server.
The JDBC API is independent of vendor-specific APIs defined by particular database systems. The JDBC architecture relies upon a Driver class that hides the details of communicating with a database server. Each database server product requires a custom Driver implementation to allow Java programs to communicate with it. Major database vendors have made JDBC drivers available for their products. In addition, a "bridge" driver exists to enable Java programs to communicate with databases through existing ODBC drivers.
The JDBC API is found in the java.sql package, which was introduced in Java 1.1. Version 1.2 of the Java 2 platform adds a number of new classes to this package to support advanced database features. Java 1.2 also provides additional features in the javax.sql standard extension package. javax.sql includes classes for treating database query results as JavaBeans, for pooling database connections, and for obtaining database connection information from a name service. The extension package also supports scrollable result sets, batch updates, and the storage of Java objects in databases.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Enterprise Computing Scenarios
The previous sections have been rapid-fire introductions to the Java Enterprise APIs. Don't worry if you didn't understand all the information presented there: the rest of the chapters in this Part cover the APIs in more detail. The important message you should take from this chapter is that the Java Enterprise APIs are building blocks that work together to enable you to write distributed Java applications for enterprise computing. The network infrastructure of every enterprise is unique, and the Java Enterprise APIs can be combined in any number of ways to meet the specific needs and goals of a particular enterprise.
Figure 1.1 shows a network schematic for a hypothetical enterprise. It illustrates some of the many possible interconnections among network services and shows the Java Enterprise APIs that facilitate those interconnections. The figure is followed by example scenarios that demonstrate how the Java Enterprise APIs might be used to solve typical enterprise computing problems. You may find it useful to refer to Figure 1.1 while reading through the scenarios, but note that the figure does not illustrate the specific scenarios presented here.
Figure 1.1: The distributed computing architecture of a hypothetical enterprise
CornCo Inc. runs a successful catalog-based mail-order business selling fresh flavored popcorn. They want to expand into the exciting world of electronic commerce over the Internet. Here's how they might do it:
  • A customer visits the company's web site, www.cornco.com, and uses a web browser to interact with the company's web server. This allows the customer to view the company's products and make selections to purchase.
  • The web server uses a shopping-cart servlet to keep track of the products the customer has chosen to buy. The HTTP protocol is itself stateless, but servlets can persist between client requests, so this shopping-cart servlet can remember the customer's selections even while the customer continues to browse the web site.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Java Enterprise APIs Versus Jini
Jini™ is the latest networking initiative from Sun. It is related to, but mostly incompatible with, the Java Enterprise APIs. Jini is a next-generation networking system designed to enable instantaneous networking between unrelated devices, without external communication. Jini is a system for distributed computing; it includes a name service, a distributed transaction service, and a distributed event service. Although these services overlap with JNDI, JTS, and JMS, Jini is fundamentally different from the Java Enterprise APIs. The Enterprise APIs are designed to bring Java into existing enterprises and to interoperate with existing protocols and services. Jini, on the other hand, is a next-generation networking system that was designed from scratch, with no concern for compatibility with today's distributed systems. Jini is a powerful and interesting technology, but covering it is beyond the scope of this book.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Chapter 2: JDBC
The JDBC API provides Java applications with mid-level access to most database systems, via the Structured Query Language (SQL). JDBC is a key enterprise API, as it's hard to imagine an enterprise application that doesn't use a database in some way. This chapter starts by demonstrating the central concepts and classes that comprise the original JDBC API (JDBC 1.0), which was introduced as an add-on to Java 1.0 and included as part of the core Java 1.1 API. It concludes with an introduction to the new JDBC 2.0 features that are provided as part of Version 1.2 of the Java 2 platform.
A word of caution: while the java.sql package is less complicated than, say, the RMI packages, it does require grounding in general database concepts and the SQL language itself. This book does include a brief SQL reference (see Chapter 8, but if you have never worked with a relational database system before, this chapter is not the place to start. For a more complete treatment of JDBC and general database concepts, I recommend Database Programming with JDBC and Java by George Reese (O'Reilly).
Different database systems have surprisingly little in common: just a similar purpose and a mostly compatible query language. Beyond that, every database has its own API that you must learn to write programs that interact with the database. This has meant that writing code capable of interfacing with databases from more than one vendor has been a daunting challenge. Cross-database APIs exist, most notably Microsoft's ODBC API, but these tend to find themselves, at best, limited to a particular platform.
JDBC is Sun's attempt to create a platform-neutral interface between databases and Java. With JDBC, you can count on a standard set of database access features and (usually) a particular subset of SQL, SQL-92. The JDBC API defines a set of interfaces that encapsulate major database functionality, including running queries, processing results, and determining configuration information. A database vendor or third-party developer writes a JDBC
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
JDBC Architecture
Different database systems have surprisingly little in common: just a similar purpose and a mostly compatible query language. Beyond that, every database has its own API that you must learn to write programs that interact with the database. This has meant that writing code capable of interfacing with databases from more than one vendor has been a daunting challenge. Cross-database APIs exist, most notably Microsoft's ODBC API, but these tend to find themselves, at best, limited to a particular platform.
JDBC is Sun's attempt to create a platform-neutral interface between databases and Java. With JDBC, you can count on a standard set of database access features and (usually) a particular subset of SQL, SQL-92. The JDBC API defines a set of interfaces that encapsulate major database functionality, including running queries, processing results, and determining configuration information. A database vendor or third-party developer writes a JDBC driver, which is a set of classes that implements these interfaces for a particular database system. An application can use a number of drivers interchangeably. Figure 2.1 shows how an application uses JDBC to interact with one or more databases without knowing about the underlying driver implementations.
Figure 2.1: JDBC-database interaction
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
JDBC Basics
Before we discuss all of the individual components of JDBC, let's look at a simple example that incorporates most of the major pieces of JDBC functionality. Example 2.1 loads a driver, connects to the database, executes some SQL, and retrieves the results. It also keeps an eye out for any database-related errors.
Example 2.1. A Simple JDBC Example
import java.sql.*;

public class JDBCSample {

  public static void main(java.lang.String[ ] args) {
    try {
      // This is where we load the driver
      Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
    } 
    catch (ClassNotFoundException e) {
      System.out.println("Unable to load Driver Class");
      return;
    }
  
    try {
      // All database access is within a try/catch block. Connect to database,
      // specifying particular database, username, and password
      Connection con = DriverManager.getConnection("jdbc:odbc:companydb",
                       "", "");
   
      // Create and execute an SQL Statement
      Statement stmt = con.createStatement();
      ResultSet rs = stmt.executeQuery("SELECT FIRST_NAME FROM EMPLOYEES");

      // Display the SQL Results
      while(rs.next()) {
        System.out.println(rs.getString("FIRST_NAME"));
      }

      // Make sure our database resources are released
      rs.close();
      stmt.close();
      con.close();

    } 
    catch (SQLException se) {
      // Inform user of any SQL errors
      System.out.println("SQL Exception: " + se.getMessage());
      se.printStackTrace(System.out);
    } 
  } 
}
Example 2.1 starts out by loading a JDBC driver class (in this case, Sun's JDBC-ODBC Bridge). Then it creates a database connection, represented by a Connection object, using that driver. With the database connection, we can create a Statement object to represent an SQL statement. Executing an SQL statement produces a ResultSet that contains the results of a query. The program displays the results and then cleans up the resources it has used. If an error occurs, a
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
JDBC Drivers
Before you can use a driver, the driver must be registered with the JDBC DriverManager. This is typically done by loading the driver class using the Class.forName() method:
try {
  Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
  Class.forName("com.oracle.jdbc.OracleDriver");
} 
catch (ClassNotFoundException e) { 
  /* Handle Exception */ 
}
One reason most programs call Class.forName() is that this method accepts a String argument, meaning that the program can store driver selection information dynamically (e.g., in a properties file).
Another way to register drivers is to add the driver classes to the jdbc.drivers property. To use this technique, add a line like the following to ~/.hotjava/properties (on Windows systems this file can be found in your Java SDK installation directory):
jdbc.drivers=com.oracle.jdbc.OracleDriver:foo.driver.dbDriver:com.al.AlDriver;
Separate the names of individual drivers with colons and be sure the line ends with a semicolon. Programs rarely use this approach, as it requires additional configuration work on the part of end users. Every user needs to have the appropriate JDBC driver classes specified in his properties file.
JDBC drivers are available for most database platforms, from a number of vendors and in a number of different flavors. There are four categories of drivers:
Type 1 JDBC-ODBC Bridge Drivers
Type 1 drivers use a bridge technology to connect a Java client to an ODBC database system. The JDBC-ODBC Bridge from Sun and InterSolv is the only extant example of a Type 1 driver. Type 1 drivers require some sort of non-Java software to be installed on the machine running your code, and they are implemented using native code.
Type 2 Native-API Partly Java Drivers
Type 2 drivers use a native code library to access a database, wrapping a thin layer of Java around the native library. For example, with Oracle databases, the native access might be through the Oracle Call Interface (OCI) libraries that were originally designed for C/C++ programmers. Type 2 drivers are implemented with native code, so they may perform better than all-Java drivers, but they also add an element of risk, as a defect in the native code can crash the Java Virtual Machine.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Connecting to the Database
The java.sql.Connection object, which encapsulates a single connection to a particular database, forms the basis of all JDBC data-handling code. An application can maintain multiple connections, up to the limits imposed by the database system itself. A standard small office or web server Oracle installation can support 50 or so connections, while a major corporate database could host several thousand. The DriverManager.getConnection() method creates a connection:
Connection con = DriverManager.getConnection("url", "user", "password");
You pass three arguments to getConnection(): a JDBC URL, a database username, and a password. For databases that do not require explicit logins, the user and password strings should be left blank. When the method is called, the DriverManager queries each registered driver, asking if it understands the URL. If a driver recognizes the URL, it returns a Connection object. Because the getConnection() method checks each driver in turn, you should avoid loading more drivers than are necessary for your application.
The getConnection() method has two other variants that are less frequently used. One variant takes a single String argument and tries to create a connection to that JDBC URL without a username or password. The other version takes a JDBC URL and a java.util.Properties object that contains a set of name/value pairs. You generally need to provide at least username = value and password = value pairs.
When a Connection has outlived its usefulness, you should be sure to explicitly close it by calling its close() method. This frees up any memory being used by the object, and, more importantly, it releases any other database resources the connection may be holding on to. These resources (cursors, handles, and so on) can be much more valuable than a few bytes of memory, as they are often quite limited. This is particularly important in applications such as servlets that might need to create and destroy thousands of JDBC connections between restarts. Because of the way some JDBC drivers are designed, it is not safe to rely on Java's garbage collection to remove unneeded JDBC connections.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Statements
Once you have created a Connection, you can begin using it to execute SQL statements. This is usually done via Statement objects. There are actually three kinds of statements in JDBC:
Statement
Represents a basic SQL statement
PreparedStatement
Represents a precompiled SQL statement, which can offer improved performance
CallableStatement
Allows JDBC programs complete access to stored procedures within the database itself
We're just going to discuss the Statement object for now; PreparedStatement and CallableStatement are covered in detail later in this chapter.
To get a Statement object, you call the createStatement() method of a Connection:
Statement stmt = con.createStatement();
Once you have created a Statement, you use it to execute SQL statements. A statement can either be a query that returns results or an operation that manipulates the database in some way. If you are performing a query, use the executeQuery() method of the Statement object:
ResultSet rs = stmt.executeQuery("SELECT * FROM CUSTOMERS");
Here we've used executeQuery() to run a SELECT statement. This call returns a ResultSet object that contains the results of the query (we'll take a closer look at ResultSet in the next section).
Statement also provides an executeUpdate() method, for running SQL statements that do not return results, such as the UPDATE and DELETE statements. executeUpdate() returns an integer that indicates the number of rows in the database that were altered.
If you don't know whether a SQL statement is going to return results (such as when the user is entering the statement in a form field), you can use the execute() method of Statement. This method returns true if there is a result associated with the statement. In this case, the
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Results
When a SQL query executes, the results form a pseudo-table that contains all rows that fit the query parameters. For instance, here's a textual representation of the results of the query string "SELECT NAME, CUSTOMER_ID, PHONE FROM CUSTOMERS":
NAME                             CUSTOMER_ID PHONE
-------------------------------- ----------- -------------------
Jane Markham                               1 617 555-1212
Louis Smith                                2 617 555-1213
Woodrow Lang                               3 508 555-7171
Dr. John Smith                             4 (011) 42 323-1239
This kind of textual representation is not very useful for Java programs. Instead, JDBC uses the java.sql.ResultSet interface to encapsulate the query results as Java primitive types and objects. You can think of a ResultSet as an object that represents an underlying table of query results, where you use method calls to navigate between rows and retrieve particular column values.
A Java program might handle the previous query as follows:
Statement stmt = con.createStatement();
ResultSet rs = stmt.executeQuery(
  "SELECT NAME, CUSTOMER_ID, PHONE FROM CUSTOMERS");

while(rs.next()) {
  System.out.print("Customer #" + rs.getString("CUSTOMER_ID"));
  System.out.print(", " + rs.getString("NAME"));
  System.out.println(", is at " + rs.getString("PHONE");
}
rs.close();
stmt.close();
Here's the resulting output:
Customer #1, Jane Markham, is at 617 555-1212
Customer #2, Louis Smith, is at 617 555-1213
Customer #3, Woodrow Lang, is at 508 555-7171
Customer #4, Dr. John Smith, is at (011) 42 323-1239
The code loops through each row of the ResultSet using the next()method. When you start working with a ResultSet, you are positioned before the first row of results. That means you have to call next() once just to access the first row. Each time you call next(), you move to the next row. If there are no more rows to read, next() returns
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Handling Errors
Any JDBC object that encounters an error serious enough to halt execution throws a SQLException. For example, database connection errors, malformed SQL statements, and insufficient database privileges all throw SQLException objects.
The SQLException class extends the normal java.lang.Exception class and defines an additional method called getNextException(). This allows JDBC classes to chain a series of SQLException objects together. SQLException also defines the getSQLState() and getErrorCode() methods to provide additional information about an error. The value returned by getSQLState() is one of the ANSI-92 SQL state codes; these codes are listed in Chapter 8. getErrorCode() returns a vendor-specific error code.
An extremely conscientious application might have a catch block that looks something like this:
try {
  // Actual database code
} 
catch (SQLException e) {
  while(e != null) { 
    System.out.println("\nSQL Exception:");
    System.out.println(e.getMessage());
    System.out.println("ANSI-92 SQL State: " + e.getSQLState());
    System.out.println("Vendor Error Code: " + e.getErrorCode());
    e = e.getNextException();
  } 
}
JDBC classes also have the option of generating (but not throwing) a SQLWarning exception when something is not quite right, but at the same time, not sufficiently serious to warrant halting the entire program. For example, attempting to set a transaction isolation mode that is not supported by the underlying database might generate a warning rather than an exception. Remember, exactly what qualifies as a warning condition varies by database.
SQLWarning encapsulates the same information as SQLException and is used in a similar fashion. However, unlike SQLException objects, which are caught in try/catch blocks, warnings are retrieved using the getWarnings() methods of the
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Prepared Statements
The PreparedStatement object is a close relative of the Statement object. Both accomplish roughly the same thing: running SQL statements. PreparedStatement, however, allows you to precompile your SQL and run it repeatedly, adjusting specific parameters as necessary. Since processing SQL strings is a large part of a database's overhead, getting compilation out of the way at the start can significantly improve performance. With proper use, it can also simplify otherwise tedious database tasks.
As with Statement, you create a PreparedStatement object from a Connection object. In this case, though, the SQL is specified at creation instead of execution, using the prepareStatement() method of Connection:
PreparedStatement pstmt = con.prepareStatement(
      "INSERT INTO EMPLOYEES (NAME, PHONE) VALUES (?, ?)");
This SQL statement inserts a new row into the EMPLOYEES table, setting the NAME and PHONE columns to certain values. Since the whole point of a PreparedStatement is to be able to execute the statement repeatedly, we don't specify values in the call to prepareStatement(), but instead use question marks (?) to indicate parameters for the statement. To actually run the statement, we specify values for the parameters and then execute the statement:
pstmt.clearParameters();
pstmt.setString(1, "Jimmy Adelphi");
pstmt.setString(2, "201 555-7823");
pstmt.executeUpdate();
Before setting parameters, we clear out any previously specified parameters with the clearParameters() method. Then we can set the value for each parameter (indexed from 1 to the number of question marks) using the setString() method. PreparedStatement defines numerous setXXX() methods for specifying different types of parameters; see the java.sql reference material later in this book for a complete list. Finally, we use the executeUpdate() method to run the SQL.
The setObject() method can insert Java object types into the database, provided that those objects can be converted to standard SQL types.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Metadata
Most JDBC programs are designed to work with a specific database and particular tables in that database; the program knows exactly what kind of data it is dealing with. Some applications, however, need to dynamically discover information about result set structures or underlying database configurations. This information is called metadata, and JDBC provides two classes for dealing with it: DatabaseMetaData and ResultSetMetaData. If you are developing a JDBC application that will be deployed outside a known environment, you need to be familiar with these interfaces.
You can retrieve general information about the structure of a database with the java.sql.DatabaseMetaData interface. By making thorough use of this class, a program can tailor its SQL and use of JDBC on the fly, to accommodate different levels of database and JDBC driver support.
Database metadata is associated with a particular connection, so DatabaseMetaData objects are created with the getMetaData() method of Connection:
DatabaseMetaData dbmeta = con.getMetaData();
DatabaseMetaData provides an overwhelming number of methods you can call to get actual configuration information about the database. Some of these return String objects (getURL()), some return boolean values (nullsAreSortedHigh()), and still others return integers (getMaxConnections()). The full list is given in Chapter 17.
A number of other methods return ResultSet objects. These methods, such as getColumns(), getTableTypes(), and getPrivileges(), generally encapsulate complex or variable-length information. The getTables() method, for instance, returns a ResultSet that contains the name of every table in the database and a good deal of extra information besides.
Many of the DatabaseMetaData
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Transactions
A transaction is a group of several operations that must behave atomically, or as if they are a single, indivisible operation. With regards to databases, transactions allow you to combine one or more database actions into a single atomic unit. If you have an application that needs to execute multiple SQL statements to fulfill one goal (say, an inventory management system that needs to move items from an INVENTORY table to a SHIPPING table), you probably want to use JDBC's transaction services to accomplish the goal.
Working with a transaction involves the following steps: start the transaction, perform its component operations, and then either commit the transaction if all the component operations succeed or roll it back if one of the operations fails. The ability to roll back a transaction is the key feature. This means that if any one SQL statement fails, the entire operation fails, and it is as though none of the component operations took place. Therefore it is impossible to end up with a situation where, for example, the INVENTORY table has been debited, but the SHIPPING table has not been credited.
Another issue with transactions and databases concerns when changes to the database become visible to the rest of the system. Transactions can operate at varying levels of isolation from the rest of the database. At the most isolated level, the results of all the component SQL statements become visible to the rest of the system only when the transaction is committed. In other words, nobody sees the reduced inventory before the shipping data is updated.
The Connection object in JDBC is responsible for transaction management. With JDBC, you are always using transactions in some form. By default, a new connection starts out in transaction auto-commit mode, which means that every SQL statement is executed as an individual transaction that is immediately committed to the database.
To perform a transaction that uses multiple statements, you have to call the
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Stored Procedures
Most RDBMS systems include some sort of internal programming language (e.g., Oracle's PL/SQL). These languages allow database developers to embed procedural application code directly within the database and then call that code from other applications. The advantage of this approach is that the code can be written just once and then used in multiple different applications (even with different platforms and languages). It also allows application code to be divorced from the underlying table structure. If stored procedures handle all of the SQL, and applications just call the procedures, only the stored procedures need to be modified if the table structure is changed later on.
Here is an Oracle PL/SQL stored procedure:
CREATE OR REPLACE PROCEDURE sp_interest
(id IN INTEGER
bal IN OUT FLOAT) IS
BEGIN
SELECT balance
INTO bal
FROM accounts
WHERE account_id = id;

bal := bal + bal * 0.03;

UPDATE accounts
SET balance = bal
WHERE account_id = id;

END;
This PL/SQL procedure takes two input values, an account ID and a balance, and returns an updated balance.
The CallableStatement interface is the JDBC object that supports stored procedures. The Connection class has a prepareCall() method that is very similar to the prepareStatement() method we used to create a PreparedStatement. Because each database has its own syntax for accessing stored procedures, JDBC defines a standardized escape syntax for accessing stored procedures with CallableStatement. The syntax for a stored procedure that does not return a result set is:
{call procedure_name[(?[,?...])]}
The syntax for a stored procedure that returns a result is:
{? = call procedure_name[(?[,?...])]}
In this syntax, each question mark (?) represents a placeholder for a procedure parameter or a return value. Note that the parameters are optional. The JDBC driver is responsible for translating the escape syntax into the database's own stored procedure syntax.
Here's a code fragment that uses
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Escape Sequences
Escape sequences allow JDBC programs to package certain database commands in a database-independent manner. Since different databases implement different features (especially scalar SQL functions) in different ways, in order to be truly portable, JDBC needs to provide a way to access at least a subset of that functionality in a standard way. We've already seen escape sequences twice: with the various SQL date and time functions, and with the CallableStatement object.
A JDBC escape sequences consists of a pair of curly braces, a keyword, and a set of parameters. Thus, call is the keyword for stored procedures, while d, t, and ts are keywords for dates and times. One keyword we haven't seen yet is escape. This keyword specifies the character that is used to escape wildcard characters in a LIKE statement:
stmt.executeQuery(
   "SELECT * FROM ApiDocs WHERE Field_Name like 'TRANS\_%' {escape '\'}");
Normally, the underscore (_) character is treated as a single-character wildcard, while the percent sign (%) is the multiple-character wildcard. By specifying the backslash (\) as the escape character, we can match on the underscore character itself. Note that the escape keyword can also be used outside wildcard searches. For example, SQL string termination characters (such as the single quote) need to be escaped when appearing within strings.
The fn keyword allows the use of internal scalar database functions. Scalar functions are a fairly standard component of most database architectures, even though the actual implementations vary. For instance, many databases support the SOUNDEX(string) function, which translates a character string into a numerical representation of its sound. Another function, DIFFERENCE(string1, string2), computes the difference between the soundex values for two strings. If the values are close enough, you can assume the two words sound the same ("Beacon" and "Bacon"). If your database supports
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
JDBC 2.0
The original JDBC API (JDBC 1.0) was first introduced as an add-on package for JDK 1.0, and it became a part of the core Java API with Java 1.1. In May 1998, Sun released the specification for JDBC 2.0. This new version of the API provides support for extended result handling, Java-aware databases, BLOB fields, and other minor improvements. All in all, there are enough new features in JDBC 2.0 to warrant a separate section in this chapter. The new version of the API is backward-compatible; code written for JDBC 1.0 compiles and runs just fine under JDBC 2.0.
The updated API ships with Version 1.2 of the Java 2 platform and is also available for download separately. As of early 1999, there are very few JDBC 2.0-compliant drivers available, although Sun and InterSolv are working towards an updated version of the JDBC-ODBC Bridge.
With JDBC 1.0, the functionality provided by the ResultSet interface is rather limited. There is no support for updates of any kind and access to rows is limited to a single, sequential read (i.e., first row, second row, third row, etc., and no going back). JDBC 2.0 supports scrollable and updateable result sets, which allows for advanced record navigation and in-place data manipulation.
With scrolling, you can move forward and backward through the results of a query, rather than just using the next() method to move to the next row. In terms of scrolling, there are now three distinct types of ResultSet objects: forward-only (as in JDBC 1.0), scroll-insensitive, and scroll-sensitive. A scroll-insensitive result set generally does not reflect changes to the underlying data, while scroll-sensitive ones do. In fact, the number of rows in a sensitive result set does not even need to be fixed.
As of JDBC 2.0, result sets are also updateable. From this perspective, there are two different kinds of result sets: read-only result sets that do not allow changes to the underlying data and updateable result sets that allow such changes, subject to transaction limitations and so on.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Chapter 3: Remote Method Invocation
This chapter examines the Java Remote Method Invocation (RMI) API—Java's native scheme for creating and using remote objects. Java RMI provides the following elements:
  • Remote object implementations
  • Client interfaces, or stubs, to remote objects
  • A remote object registry for finding objects on the network
  • A network protocol for communication between remote objects and their client
  • A facility for automatically creating (activating) remote objects on-demand
Each of these elements (except the last one) has a Java interface defined for it within the java.rmi package and its subpackages, which comprise the RMI API. Using these interfaces, you can develop remote objects and the clients that use them to create a distributed application that resides on hosts across the network.
RMI is the distributed object system that is built into the core Java environment. You can think of RMI as a built-in facility for Java that allows you to interact with objects that are actually running in Java virtual machines on remote hosts on the network. With RMI (and other distributed object APIs we discuss in this book), you can get a reference to an object that "lives" in a remote process and invoke methods on it as if it were a local object running within the same virtual machine as your code (hence the name, "Remote Method Invocation API").
RMI was added to the core Java API in Version 1.1 of the JDK (and enhanced for Version 1.2 of the Java 2 platform), in recognition of the critical need for support for distributed objects in distributed-application development. Prior to RMI, writing a distributed application involved basic socket programming, where a "raw" communication channel was used to pass messages and data between two remote processes. Now, with RMI and distributed objects, you can "export" an object as a remote object, so that other remote processes/agents can access it directly as a Java object. So, instead of defining a low-level message protocol and data transmission format between processes in your distributed application, you use Java interfaces as the "protocol" and the exported method arguments become the data transmission format. The distributed object system (RMI in this case) handles all the underlying networking needed to make your remote method calls work.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Introduction to RMI
RMI is the distributed object system that is built into the core Java environment. You can think of RMI as a built-in facility for Java that allows you to interact with objects that are actually running in Java virtual machines on remote hosts on the network. With RMI (and other distributed object APIs we discuss in this book), you can get a reference to an object that "lives" in a remote process and invoke methods on it as if it were a local object running within the same virtual machine as your code (hence the name, "Remote Method Invocation API").
RMI was added to the core Java API in Version 1.1 of the JDK (and enhanced for Version 1.2 of the Java 2 platform), in recognition of the critical need for support for distributed objects in distributed-application development. Prior to RMI, writing a distributed application involved basic socket programming, where a "raw" communication channel was used to pass messages and data between two remote processes. Now, with RMI and distributed objects, you can "export" an object as a remote object, so that other remote processes/agents can access it directly as a Java object. So, instead of defining a low-level message protocol and data transmission format between processes in your distributed application, you use Java interfaces as the "protocol" and the exported method arguments become the data transmission format. The distributed object system (RMI in this case) handles all the underlying networking needed to make your remote method calls work.
Java RMI is a Java-only distributed object scheme; the objects in an RMI-based distributed application have to be implemented in Java. Some other distributed object schemes, most notably CORBA, are language-independent, which means that the objects can be implemented in any language that has a defined binding. With CORBA, for example, bindings exist for C, C++, Java, Smalltalk, and Ada, among other languages.
The advantages of RMI primarily revolve around the fact that it is "Java-native." Since RMI is part of the core Java API and is built to work directly with Java objects within the Java VM, the integration of its remote object facilities into a Java application is almost seamless. You really can use RMI-enabled objects as if they live in the local Java environment. And since Java RMI is built on the assumption that both the client and server are Java objects, RMI can extend the internal garbage-collection mechanisms of the standard Java VM to provide distributed garbage collection of remotely exported objects.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Defining Remote Objects
Now that you have a basic idea of how Java RMI works, we can explore the details of creating and using distributed objects with RMI. As I mentioned earlier, defining a remote RMI object involves specifying a remote interface for the object, then providing a class that implements this interface. The remote interface and implementation class are then used by RMI to generate a client stub and server skeleton for your remote object. The communication between local objects and remote objects is handled using these client stubs and server skeletons. The relationships among stubs, skeletons, and the objects that use them are shown in Figure 3.2.
Figure 3.2: Relationships among remote object, stub, and skeleton classes
When a client gets a reference to a remote object (details on how this reference is obtained come later) and then calls methods on this object reference, there needs to be a way for the method request to get transmitted back to the actual object on the remote server and for the results of the method call to get transmitted back to the client. This is what the generated stub and skeleton classes are for. They act as the communication link between the client and your exported remote object, making it seem to the client that the object actually exists within its Java VM.
The RMI compiler (rmic) automatically generates these stub and skeleton classes for you. Based on the remote interface and implementation class you provide, rmic generates stub and skeleton classes that implement the remote interface and act as go-betweens for the client application and the actual server object. For the client stub class, the compiler generates an implementation of each remote method that simply packages up (marshals) the method arguments and transmits them to the server. For the server skeleton class, the RMI compiler generates another set of implementations of the remote methods, but these are designed to receive the method arguments from the remote method call, unpackage them, and make the corresponding method call on the object implementation. Whatever the method call generates (return data or an exception), the results are packaged and transmitted back to the remote client. The client stub method (which is still executing at this point) unpackages the results and delivers them to the client as the result of its remote method call.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Creating the Stubs and Skeletons
After you define the remote Java interface and implementation class, you compile them into Java bytecodes using a standard Java compiler. Then you use the RMI stub/skeleton compiler, rmic, to generate the stub and skeleton interfaces that are used at either end of the RMI communication link, as was shown in Figure 3.1. In its simplest form, you can run rmic with the fully qualified classname of your implementation class as the only argument. For example, once we've compiled the ThisOrThatServer and ThisOrThatServerImpl classes, we can generate the stubs and skeletons for the remote ThisOrThatServer object with the following command (Unix version):
% rmic ThisOrThatServerImpl
If the RMI compiler is successful, this command generates the stub and skeleton classes, ThisOrThatServerImpl_Stub and ThisOrThatServerImpl_Skel, in the current directory. The rmic compiler has additional arguments that let you specify where the generated classes should be stored, whether to print warnings, etc. For example, if you want the stub and skeleton classes to reside in the directory /usr/local/classes, you can run the command using the -d option:
% rmic -d /usr/local/classes ThisOrThatServerImpl
This command generates the stub and skeleton classes in the specified directory. A full description of the rmic utility and its options is given in Chapter 9, RMI Tools.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Accessing Remote Objects as a Client
Now that we've defined a remote object interface and its server implementation and generated the stub and skeleton classes that RMI uses to establish the link between the server object and the remote client, it's time to look at how you make your remote objects available to remote clients.
The first remote object reference in an RMI distributed application is typically obtained through the RMI registry facility and the Naming interface. Every host that wants to export remote references to local Java objects must be running an RMI registry daemon of some kind. A registry daemon listens (on a particular port) for requests from remote clients for references to objects served on that host. The standard Sun Java SDK distribution provides an RMI registry daemon, rmiregistry. This utility simply creates a Registry object that listens to a specified port and then goes into a wait loop, waiting for local processes to register objects with it or for clients to connect and look up RMI objects in its registry. You start the registry daemon by running the rmiregistry command, with an optional argument that specifies a port to listen to:
objhost% rmiregistry 5000 &
Without the port argument, the RMI registry daemon listens on port 1099. Typically, you run the registry daemon in the background (i.e., put an & at the end of the command on a Unix system or run start rmiregistry [ port ] in a DOS window on a Windows system) or run it as a service at startup.
Once the RMI registry is running on a host, you can register remote objects with it using one of these classes: the java.rmi.registry.Registry interface, the java.rmi.registry.LocateRegistry class, or the java.rmi.Naming class.
A
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Dynamically Loaded Classes
The RMI runtime system has a dynamic class-loading facility that loads the classes it needs while executing remote method calls. In some situations, you don't need to worry much about how your application classes are obtained by the various agents in an RMI application. This is especially true if you have direct access to all hosts involved in the distributed system (i.e., if you can install your application classes in the local CLASSPATH for each machine participating in the application). For instance, when discussing the earlier Account example, I assumed that all the relevant classes (Account, AccountImpl, stub, and skeleton classes) were installed on both the client and the server. However, if your distributed application involves remote agents running on hosts that are not directly under your control, you need to understand how RMI loads classes at runtime, so you can ensure that each remote agent can find the classes it needs in order to run.
As with any Java application, the Java runtime system is responsible for loading the classes needed to initiate an RMI session. Starting an interaction with a remote object means loading the RMI API classes themselves, as well as the base interface for the remote object and the stub class for the remote interface. On the server side, the skeleton class for the remote object and the actual implementation class need to be loaded in order to run the server object that is being remotely exported.
The classes that are referenced directly by a given Java class are normally loaded by the same class loader that loaded the class itself. So, in an RMI client that does a Naming lookup to find a remote object, the stub interface for the remote object is loaded using the class loader for the class doing the lookup. If the RMI client is a Java application (started using the java command to invoke the main() method on an object), the default (local) class loader tries to find the remote interface locally, from the local CLASSPATH. If the RMI client is an applet loaded in a web page, the
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Remote Object Activation
Automatic activation of remote objects is a new feature in RMI as of Java 1.2. The activation subsystem in RMI provides you with two basic features: the ability to have remote objects instantiated (activated) on-demand by client requests, and the ability for remote object references to remain valid across server crashes, making the references persistent. These features can be quite useful in certain types of distributed applications.
For example, think back to the AccountManager class we discussed when we talked about factory objects. We might not want to keep the AccountManager running on our server 24 hours a day; perhaps it consumes lots of server resources (memory, database connections, etc.), so we don'