Cover | Table of Contents | Colophon
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.
java.sql
package, which was introduced in
Java 1.1. Java 2 (including the 1.2, 1.3, and 1.4 versions) updated
the core APIs to use JDBC 2.0, which adds a number of new classes to
this package to support advanced database features. JDBC 2.0 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. AS
of this printing, the JDBC 3.0 specifications have been finalized and
should be available in mid-2002.
http://java.sun.com/xml). We
discuss JAXP in some detail in the book (see ). There are others not covered in this book,
such as the Java API for XML Messaging (JAXM), the Java API for XML
Registries (JAXR), which supports ebXML. and the Java API for XML
Binding (JAXB), which provides a means for marshalling and
unmarshalling Java Objects to and from XML representations.
http://www.apache.org), OASIS
(http://www.oasis-open.org) and
others. At the time of this writing, the subject of XML-based web
services is somewhat unfocused in the market, and more complete
coverage requires much more material than we could provide in this
Nutshell book. However, the Java XML APIs will receive more coverage
in their own volume once they mature.
http://www.sun.com/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 these J2EE 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.
java.sql
package is not tremendously complex, it
does require grounding in general database concepts and the SQL
language itself. This book includes a brief SQL reference (see , 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, we recommend Database Programming with JDBC and
Java by George Reese (O'Reilly).
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);
}
}
}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");
getConnection( ): a
JDBC URL, a database
username,
and a password. For databases that
don't 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.
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, or with a username and password embedded in the
URL itself. 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.
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.
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
PreparedStatement
CallableStatement
Statement object for now;
PreparedStatement and
CallableStatement are covered in detail later in
this chapter.
Statement object, call the
createStatement( ) method of a
Connection:
Statement stmt = con.createStatement( );
Statement, 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");
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 don't return
results, such as the 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
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.
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( );
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
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 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.
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 . getErrorCode(
) returns a vendor-specific error code.
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( );
}
}
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 SQLExceptionPreparedStatement 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.
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 (?, ?)");
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( );
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( )BLOB and CLOB data
using the getBinaryStream( ) or
getAsciiStream( ) methods. (A third method,
getUnicodeStream( ), has been deprecated in favor
of the new getCharacterStream( ) method, which
returns a Reader.)
ResultSet interface includes getBlob(
) and getClob( ) methods, which return
Blob and Clob objects,
respectively. The Blob and Clob
objects themselves allow access to their data via streams (the
getBinaryStream( ) method of
Blob and the getCharacterStream(
) method of Clob) or via direct-read
methods (the getBytes( ) method
of Blob and the getSubString( )
method of Clob).
Clob object and call the
getCharacterStream( ) method:
String s;
Clob clob = blobResultSet.getBlob("CLOBFIELD");
BufferedReader clobData = new BufferedReader(clob.getCharacterStream( ));
while((s = clobData.readLine( )) != null)
System.out.println(s);
Blob and Clob objects when you
are working with a PreparedStatement, using the
setBlob( ) and setClob( ) methods. While the API
provides update methods for streams, there are no
updateBlob( ) or updateClob( )
methods, and the Blob interface provides no
mechanism for altering the contents of a Blob
already stored in the database (although some drivers support
updating of BLOB and CLOB types via the setBinaryStream(
) and 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.
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.
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( )).
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 as well as a good deal of extra information.
INVENTORY table to a SHIPPING
table), you probably want to use JDBC's transaction
services to accomplish the goal.
INVENTORY table has been debited,
but the SHIPPING table has not been credited.
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.
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;
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 doesn't return a result set is:
{call procedure_name[(?[,?...])]}
{? = call procedure_name[(?[,?...])]}
?) 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.
CallableStatement object.
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 '\'}");
_ ) 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.
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
javax.sql package
is an optional extension of the JDBC 2.1 API. It includes support for
a variety of enterprise-development activities. It's
a standard component of the J2EE platform, and the supporting classes
can also be downloaded separately for use with any Java 2 system.
DataSource
interface provides an alternative to
the DriverManager class and conventional JDBC
URLs. Instead, information about a database is stored within a naming
service and retrieved via the JNDI API. Connection information
(drivers, server locations, and so forth) are stored within the
DataSource object, which uses them to create the
actual Connection object used to execute JDBC
commands. DataSource objects are also used to
provide native driver-level support for connection pooling and
distributed transactions.
DataSource is assigned a logical name, by
convention beginning with "jdbc/".
The logical name and associated connection metadata are configured in
the J2EE setup process. This makes code more portable and allows for
easy changes in drivers and connection information. Accessing a
DataSource via JNDI is very simple:
Context ctx = new InitialContext( );
DataSource ds = (DataSource)ctx.lookup("jdbc/CamelDB");
Connection con = ds.getConnection("lawrence", "arabia");
DataSource object
from the naming service. The getConnection( )
method of DataSource then logs into the database
and returns a Connection object. Unlike
DriverManager, the only information required is a
username and password.
ConnectionPoolDataSource
provides a transparent interface to a
"pool" of available
ConnectionDatabaseMetaData
interface, allowing programmers to
determine which new features are supported.
setSavepoint(String
name)
method of Connection creates a named savepoint in
the current transaction, and returns an object implementing the
Savepoint interface. The object can be passed to
the rollback(
)
method of
Connection to roll back all components of the
current transaction that took place after the setSavepoint(
) method was called:
Statement stmt = con.createStatement( );
stmt.executeUpdate("delete from clients");
stmt.executeUpdate("insert into clients (NAME, ID) values ('Charles Babbage', 1)");
Savepoint save = con.setSavepoint("INSERT_POINT");
stmt.executeUpdate("update clients set NAME = 'Ada Lovelace' where ID = 1");
con.rollback(save);
con.commit( );
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.
java.net and java.io packages,
allow you to open up very narrow, low-level data connections to your
Java process, for simple data exchanges or
"heartbeat" purposes (make a
successful connection and transmit a few bytes to confirm that a
process is alive). RMI and other remote object systems fall somewhere
in-between the two. They allow you to export functionality at the
object level, allowing remote clients to interact directly with
individual objects, in the same way they do with local objects: using
(remote) method calls.
Account and AccountImpl
classes, we can generate the stubs and skeletons for the remote
Account object with the following command (Unix
version):
% rmic AccountImpl
AccountImpl_Stub and
AccountImpl_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 AccountImpl
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 &
& 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.
java.rmi.registry.Registry interface,
the java.rmi.registry.LocateRegistry class, or the
java.rmi.Naming class.
CLASSPATH for
each machine participating in the application). For instance, when
discussing the earlier Account example, we assumed
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
aren't 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.