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. 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.
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).
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);
}
}
}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 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 */
}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).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;
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 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.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.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, you call the createStatement() method of a Connection:Statement stmt = con.createStatement();
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");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.execute() method of Statement. This method returns true if there is a result associated with the statement. In this case, 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
next(), you move to the next row. If there are no
more rows to read, next() returns
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 Chapter 8. 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 SQLException objects, which are caught in try/catch blocks, warnings are retrieved using the getWarnings() methods of 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.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() method to run the SQL.setObject() method can insert Java object types into the database, provided that those objects can be converted to standard SQL types. 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()). The full list is
given in Chapter 17.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.DatabaseMetaDataINVENTORY 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 does not 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 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.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.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.
ThisOrThatServer and
ThisOrThatServerImpl classes, we can generate the
stubs and skeletons for the remote
ThisOrThatServer object with the following
command (Unix version):% rmic ThisOrThatServerImpl
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
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, 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.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 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'