The Java 2 platform has brought a new release of the JDBC specification, JDBC 2.0. The old JDBC spec, 1.2, was a fairly simple, single package that supported just about all of the common things you might want to do with a database in Java. JDBC 1.2, however, predated many of the key specifications in use today, like JNDI (Java Naming and Directory Interface). It also omitted some of the more-complex functionality required by large-scale Java applications, like connection pooling and distributed transaction support.
JDBC 2.0 addresses the corner cases missed by JDBC 1.2. In order to keep the core functionality as simple as possible, the JDBC team divided JDBC up into a core package and a standard extension. The core JDBC package contains everything you would have found in the old JDBC 1.2 API, with a few new additions. The standard extension, on the other hand, is entirely new functionality in a new package that is not in the standard JDK release. Instead, like other extension packages, the JDBC 2.0 Standard Extension classes appear under the javax namespace, specifically in the javax.sql package. You can download this package from Sun’s Java Web site.
The JDBC 2.0 Standard Extension introduces the following features to database development in Java:
- JNDI Support for JDBC Data Sources
- Connection Pooling
- Rowsets
- Distributed Transactions
For the most part, these features are entirely transparent to developers. In fact, support for connection pooling and distributed transactions requires almost no new knowledge on the part of the developer. This article explores the part of the JDBC 2.0 Standard Extension that will have the most impact on developers, the JNDI support.
JNDI
Though a full discussion of JNDI is well beyond the scope of this article, a short overview is certainly in order. JNDI is a generic interface for accessing objects stored in a naming and directory service. Examples of naming and directory services include DNS services, LDAP services, NIS, and even file systems. Their job is to associate names with information. DNS, for example, associates a computer name like www.imaginary.com with an IP address like 209.98.32.12. The advantage of maintaining this association is twofold. First of all, www.imaginary.com is clearly simpler to remember than 209.98.32.12. Additionally, the physical location of the machine can be changed without impacting its name. In other words, the Web pages served at www.imaginary.com can be moved to 209.98.32.13 without anything breaking. After all, people look up those Web pages using the name www.imaginary.com. Because DNS manages the mapping of the computer name to the IP address, the change is invisible to end users.There are many different kinds of naming and directory services. DNS associates computer names with IP addresses. Similarly, file systems associate file names with physical locations on a hard drive. JNDI provides a single API for accessing any information stored in any naming and directory service. The following code shows how you might look up a printer using JDNI:
javax.naming.Context ctx = new InitialContext();
Printer p = ctx.lookup(“Color Printer”);
The javax.naming package is an extension package that comes separately from the JDK core classes. The Context interface in the JNDI package is your tool for looking up resources. In this example, we look up a Printer object stored under the name “Color Printer.” All of the information associated with that printer is stored in the Printer object representing it.
JDBC and JNDI
One of the weak points of the JDBC 1.2 specification was the complexity of connecting to a database. The connection process is the only commonly used part of the JDBC specification that requires knowledge of the specific database environment in which you are working. In order to make a connection, you have to know the name of the JDBC driver you are using, the URL string that specifies connection information, and any connection parameters. You can use command line arguments to avoid hard coding this information into your Java classes, but the path of least resistance is to stick the JDBC URL and driver name right in your Java code. Furthermore, knowledge of connecting to one database does not translate to knowledge of connecting to another. The result is that the database connection is the most error prone part of database programming in Java.It seems only natural to store all of this environment-specific information for database connections in a naming and directory service and access it by name. Using this approach, a programmer would not have to know any of the information required to make a database connection; it would simply get some reference to the database from the naming and directory service that contains all of the information. The new JNDI support in the JDBC 2.0 Standard Extension enables this approach to JDBC programming.
The key to JNDI support in JDBC is the javax.sql.DataSource interface. A JDBC driver that supports the JDBC Standard Extension functionality provides an implementation of this interface. The implementation will have as its attributes any information necessary to make a connection to a database in the database engine it supports. For example, the DataSource implementation for the mSQL-JDBC has attributes that store the name of the machine on which the database runs, the port it is listening to, and the name of the database to which you wish to connect. This DataSource object is then stored in a JNDI directory service with all of these attributes. Your application code needs to know only the name under which this DataSource object is stored. The code to make a connection using the JDBC 2.0 Standard Extension support is now this simple:
javax.naming.Context ctx =
new InitialContext();
javax.sql.DataSource ds =
(DataSource)ctx.lookup(“example”);
java.sql.Connection conn =
ds.getConnection();
[Ed.: Code broken above and below for presentation.]
Compare the simplicity of this paradigm to the old way:
Class.forName(driverName).newInstance();
java.sql.Connection conn =
DriverManager.getConnection(jdbcURL, uid, password);
The old way requires you to know the name of the JDBC driver you are using and to load it. Once it is loaded, your code has to know the URL to use for the connection. The new way, however, requires no knowledge about the database in question. All of that data is associated with a DataSource in the JNDI directory. If you change anything about your environment, such as the machine on which the database server is running, you only change the entry in the JNDI directory. You do not need to make any application code changes. The following example code shows in detail how to get a DataSource into a JNDI service and then get it back out.
Getting started
Not only is the JNDI approach to making a database connection in JDBC simpler and less error prone, it is the stated future direction of JDBC. The JDBC 2.0 Standard Extension states that the JNDI approach may eventually become the preferred method of making a database connection. It is therefore a good idea to start taking advantage of this powerful tool today if your driver supports it. In order to get started, you need some tools not found in the standard JDK distributions:
- The JDBC 2.0 Standard Extension API package
- The JNDI API Package
- A JNDI-supported naming and directory service
- A JNDI service provider for the naming and directory service you are using
If you just want to play around, Sun provides a file-system based service provider that lets you store Java objects in your file system and access them through JNDI. You can download this service provider from the JNDI home page.
About the author
George Reese is a software engineer for Ancept, Inc. and lives in Maple Grove, Minn. He specializes in the architecture of multitier distributed enterprise Java systems. He is the author of Database Programming with JDBC and Java, from O’Reilly and Associates.
import java.sql.*;
import java.util.*;
import javax.naming.*;
import javax.sql.*;public class JNDIUser {
public static void main(String args[]) {
if( args.length > 0 && args[0].equals(“install”) ) {
try {
registerDataSource();
System.out.println(“Data source ‘jdbc/test’ installed.”);
}
catch( Exception e ) {
e.printStackTrace();
System.out.println(“Install failed.”);
}
return;
}
try {
Context ctx = new InitialContext();
DataSource ds = (DataSource)ctx.lookup(“/tmp/jdbc/test”);
Connection con = ds.getConnection();
Statement stmt;
ResultSet rs;stmt = con.createStatement();
rs = stmt.executeQuery(“SELECT test_id, test_int, test_date, ” +
“test_char, test_val ” +
“FROM test ORDER BY test_id”);
System.out.println(“Got results:”);
while( rs.next() ) {
int i = rs.getInt(1);
String s, comma = “”;
java.util.Date d;System.out.print(“tkey: ” + i + “(“);
i = rs.getInt(2);
if( !rs.wasNull() ) {
System.out.print(“test_int=” + i);
comma = “,”;
}
d = rs.getDate(3);
if( !rs.wasNull() ) {
System.out.print(comma + “test_date=” + d);
comma = “,”;
}
s = rs.getString(4);
if( !rs.wasNull() ) {
System.out.print(comma + “test_char='” + s + “‘”);
comma = “,”;
}
s = rs.getString(5);
if( !rs.wasNull() ) {
System.out.print(comma + “test_val='” + s + “‘”);
}
System.out.println(“)”);
}
con.close();
System.out.println(“Done.”);
}
catch( Exception e ) {
e.printStackTrace();
}
}static public void registerDataSource() throws Exception {
com.imaginary.sql.msql.MsqlDataSource ds;
Context ctx;ctx = new InitialContext();
ds = new com.imaginary.sql.msql.MsqlDataSource();
ds.setServerName(“carthage.imaginary.com”);
ds.setDatabaseName(“test”);
ctx.bind(“/tmp/jdbc/test”, ds);
ctx.close();
}
}