NOTE: The material in this chapter is based on JDBCtm API Tutorial and Reference, Second Edition: Universal Data Access for the Javatm 2 Platform, published by Addison Wesley as part of the Java series, ISBN 0-201-43328-1.
A DataSource
object is the representation of a data source in the Java programming language. In basic terms, a data source is a facility for storing data. It can be as sophisticated as a complex database for a large corporation or as simple as a file with rows and columns. A data source can reside on a remote server, or it can be on a local desktop machine. Applications access a data source using a connection, and a DataSource
object can be thought of as a factory for connections to the particular data source that the DataSource
instance represents. The DataSource
interface provides two methods for establishing a connection with a data source.
Using a DataSource
object is the preferred alternative to using the DriverManager
for establishing a connection to a data source. They are similar to the extent that the DriverManager
class and DataSource
interface both have methods for creating a connection, methods for getting and setting a timeout limit for making a connection, and methods for getting and setting a stream for logging.
Their differences are more significant than their similarities, however. Unlike the DriverManager
, a DataSource
object has properties that identify and describe the data source it represents. Also, a DataSource
object works with a Javatm Naming and Directory Interfacetm (JNDI) naming service and is created, deployed, and managed separately from the applications that use it. A driver vendor will provide a class that is a basic implementation of the DataSource
interface as part of its JDBC 2.0 or 3.0 driver product. What a system administrator does to register a DataSource
object with a JNDI naming service and what an application does to get a connection to a data source using a DataSource
object registered with a JNDI naming service are described later in this chapter.
Being registered with a JNDI naming service gives a DataSource
object two major advantages over the DriverManager
. First, an application does not need to hardcode driver information, as it does with the DriverManager
. A programmer can choose a logical name for the data source and register the logical name with a JNDI naming service. The application uses the logical name, and the JNDI naming service will supply the DataSource
object associated with the logical name. The DataSource
object can then be used to create a connection to the data source it represents.
The second major advantage is that the DataSource
facility allows developers to implement a DataSource
class to take advantage of features like connection pooling and distributed transactions. Connection pooling can increase performance dramatically by reusing connections rather than creating a new physical connection each time a connection is requested. The ability to use distributed transactions enables an application to do the heavy duty database work of large enterprises.
Although an application may use either the DriverManager
or a DataSource
object to get a connection, using a DataSource
object offers significant advantages and is the recommended way to establish a connection.
A DataSource
object has a set of properties that identify and describe the real world data source that it represents. These properties include information like the location of the database server, the name of the database, the network protocol to use to communicate with the server, and so on. DataSource
properties follow the JavaBeans design pattern and are usually set when a DataSource
object is deployed.
To encourage uniformity among DataSource
implementations from different vendors, the JDBC 2.0 API specifies a standard set of properties and a standard name for each property. The following table gives the standard name, the data type, and a description for each of the standard properties. Note that a DataSource
implementation does not have to support all of these properties; the table just shows the standard name that an implementation should use when it supports a property.
dataSourceName |
the logical name for the underlying | |
networkProtocol | ||
A DataSource
object will, of course, have to support all of the properties that the data source it represents needs for making a connection, but the only property that all DataSource
implementations are required to support is the description
property. This standardizing of properties makes it possible, for instance, for a utility to be written that lists available data sources, giving a description of each along with the other property information that is available.
A DataSource
object is not restricted to using only those properties specified in Table 4.1. A vendor may add its own properties, in which case it should give each new property a vendor-specific name.
If a DataSource
object supports a property, it must supply getter
and setter
methods for it. The following code fragment illustrates the methods that a DataSource
object ds would need to include if it supports, for example, the property serverName
.
ds.setServerName("my_database_server"); String serverName = ds.getServerName();
Properties will most likely be set by a developer or system administrator using a GUI tool as part of the installation of the data source. Users connecting to the data source do not get or set properties. This is enforced by the fact that the DataSource
interface does not include the getter
and setter
methods for properties; they are supplied only in a particular implementation. The effect of including getter
and setter
methods in the implementation but not the public interface creates some separation between the management API for DataSource
objects and the API used by applications. Management tools can get at properties by using introspection.
JNDI provides a uniform way for an application to find and access remote services over the network. The remote service may be any enterprise service, including a messaging service or an application-specific service, but, of course, a JDBC application is interested mainly in a database service. Once a DataSource
object is created and registered with a JNDI naming service, an application can use the JNDI API to access that DataSource
object, which can then be used to connect to the data source it represents.
A DataSource
object is usually created, deployed, and managed separately from the Java applications that use it. For example, the following code fragment creates a DataSource
object, sets its properties, and registers it with a JNDI naming service. Note that a DataSource
object for a particular data source is created and deployed by a developer or system administrator, not the user. The class VendorDataSource
would most likely be supplied by a driver vendor. (The code example in the next section will show the code that a user would write to get a connection.) Note also that a GUI tool will probably be used to deploy a DataSource
object, so the following code, shown here mainly for illustration, is what such a tool would execute.
VendorDataSource vds = new VendorDataSource();ctx.bind("jdbc/AcmeDB", vds);vds.setServerName("my_database_server"); vds.setDatabaseName("my_database"); vds.setDescription("the data source for inventory and personnel"); Context ctx = new InitialContext();
The first four lines represent API from a vendor's class VendorDataSource
, an implementation of the javax.sql.DataSource
interface. They create a DataSource
object, vds, and set its serverName
, databaseName
, and description
properties. The fifth and sixth lines use JNDI API to register vds with a JNDI naming service. The fifth line calls the default InitialContext
constructor to create a Java object that references the initial JNDI naming context. System properties, which are not shown here, tell JNDI which naming service provider to use. The last line associates vds with a logical name for the data source that vds represents.
The JNDI namespace consists of an initial naming context and any number of subcontexts under it. It is hierarchical, similar to the directory/file structure in many file systems, with the initial context being analogous to the root of a file system and subcontexts being analogous to subdirectories. The root of the JNDI hierarchy is the initial context, here represented by the variable ctx. Under the initial context there may be many subcontexts, one of which is jdbc
, the JNDI subcontext reserved for JDBC data sources. (The logical data source name may be in the subcontext jdbc
or in a subcontext under jdbc
.) The last element in the hierarchy is the object being registered, analogous to a file, which in this case is a logical name for a data source. The result of the preceding six lines of code is that the VendorDataSource
object vds is associated with jdbc/AcmeDB
. The following section shows how an application uses this to connect to a data source.
In the previous section, a DataSource
object, vds, was given properties and bound to the logical name AcmeDB
. The following code fragment shows application code that uses this logical name to connect to the database that vds represented. The code then uses the connection to print lists with the name and title of each member of the sales and customer service departments.
Context ctx = new InitialContext();con.close();DataSource ds = (DataSource)ctx.lookup("jdbc/AcmeDB"); Connection con = ds.getConnection("genius", "abracadabra"); con.setAutoCommit(false); PreparedStatement pstmt = con.prepareStatement( "SELECT NAME, TITLE FROM PERSONNEL WHERE DEPT = ?"); pstmt.setString(1, "SALES"); ResultSet rs = pstmt.executeQuery(); System.out.println("Sales Department:"); while (rs.next()) { String name = rs.getString("NAME"); String title = rs.getString("TITLE"); System.out.println(name + " " + title); } pstmt.setString(1, "CUST_SERVICE"); ResultSet rs = pstmt.executeQuery(); System.out.println("Customer Service Department:"); while (rs.next()) { String name = rs.getString("NAME"); String title = rs.getString("TITLE"); System.out.println(name + " " + title); } rs.close(); pstmt.close();
The first two lines use JNDI API; the third line uses DataSource
API. After the first line creates an instance of javax.naming.Context
for the initial naming context, the second line calls the method lookup
on it to get the DataSource
object associated with jdbc/AcmeDB
. Recall that in the previous code fragment, the last line of code associated jdbc/AcmeDB
with vds, so the object returned by the lookup
method refers to the same DataSource
object that vds represented. However, the return value for the method lookup
is a reference to a Java Object
, the most generic of objects, so it must be cast to the more narrow DataSource
before it can be assigned to the DataSource
variable ds.
At this point ds refers to the same data source that vds referred to previously, the database my_database on the server my_database_server. Therefore, in the third line of code, calling the method DataSource.getConnection
on ds and supplying it with a user name and password is enough to create a connection to my_database.
The rest of the code fragment uses a single transaction to execute two queries and print the results of each query. The DataSource
implementation in this case is a basic implementation included with the JDBC driver. If the DataSource
class had been implemented to work with an XADataSource
class, and the preceding code example was executed in the context of a distributed transaction, the code could not have called the method Connection.commit
. It also would not have set the auto-commit mode to false
because that would have been unnecessary. The default for newly-created connections that can participate in distributed transactions is to have auto-commit mode turned off. The next section will discuss the three broad categories of DataSource
implementations.
In addition to the version of getConnection
that takes a user name and password, the DataSource
interface provides a version of the method DataSource.getConnection
that takes no parameters. It is available for situations where a data source does not require a user name and password because it uses a different security mechanism or where a data source does not restrict access.
The DataSource
interface may be implemented to provide three different kinds of connections. As a result of DataSource
objects working with a JNDI service provider, all connections produced by a DataSource
object offer the advantages of portability and easy maintenance, which are explained later in this chapter. Implementations of DataSource
that work with implementations of the more specialized ConnectionPoolDataSource
and XADataSource
interfaces produce connections that are pooled or that can be used in distributed transactions. The following list summarizes the three general categories of classes that implement the DataSource
interface:
DataSource
class
DataSource
class implemented to provide connection pooling
ConnectionPoolDataSource
class, which is always provided by a driver vendor
DataSource
class implemented to provide distributed transactions
XADataSource
class, which is always provided by a driver vendor
Note that a DataSource
implementation that supports distributed transactions is almost always implemented to support connection pooling as well.
An instance of a class that implements the DataSource
interface represents one particular data source. Every connection produced by that instance will reference the same data source. In a basic DataSource
implementation, a call to the method DataSource.getConnection
returns a Connection
object that, like the Connection
object returned by the DriverManager
facility, is a physical connection to the data source. Appendix A of the specification for the JDBC 2.0 Standard Extension API (available at http://java.sun.com/products/jdbc
) gives a sample implementation of a basic DataSource
class.
DataSource
objects that implement connection pooling likewise produce a connection to the particular data source that the DataSource
class represents. The Connection
object that the method DataSource.getConnection
returns, however, is a handle to a PooledConnection
object rather than being a physical connection. An application uses the Connection
object just as it usually does and is generally unaware that it is in any way different. Connection pooling has no effect whatever on application code except that a pooled connection, as is true with all connections, should always be explicitly closed. When an application closes a connection that is pooled, the connection joins a pool of reusable connections. The next time DataSource.getConnection
is called, a handle to one of these pooled connections will be returned if one is available. Because connection pooling avoids creating a new physical connection every time one is requested, it can help to make applications run significantly faster. Connection pooling is generally used, for example, by a web server that supports servlets and JavaServertm Pages.
A DataSource
class can likewise be implemented to work with a distributed transaction environment. An EJB server, for example, supports distributed transactions and requires a DataSource
class that is implemented to interact with it. In this case, the DataSource.getConnection
method returns a Connection
object that can be used in a distributed transaction. As a rule, EJB servers provide a DataSource
class that supports connection pooling as well as distributed transactions. Like connection pooling, transaction management is handled internally, so using distributed transactions is easy. The only requirement is that when a transaction is distributed (involves two or more data sources), the application cannot call the methods commit
or rollback
. It also cannot put the connection in auto-commit mode. The reason for these restrictions is that a transaction manager begins and ends a distributed transaction under the covers, so an application cannot do anything that would affect when a transaction begins or ends.
The DataSource
interface provides methods that allow a user to get and set the character stream to which tracing and error logging will be written. A user can trace a specific data source on a given stream, or multiple data sources can write log messages to the same stream provided that the stream is set for each data source. Log messages that are written to a log stream specific to a DataSource
object are not written to the log stream maintained by the DriverManager
.
There are major advantages to connecting to a data source using a DataSource
object registered with a JNDI naming service rather than using the DriverManager
facility. The first is that it makes code more portable. With the DriverManager
, the name of a JDBC driver class, which usually identifies a particular driver vendor, is included in application code. This makes the application specific to that vendor's driver product and thus non-portable.
Another advantage is that it makes code much easier to maintain. If any of the necessary information about the data source changes, only the relevant DataSource
properties need to be modified, not every application that connects to that data source. For example, if a database is moved to a different server and uses a different port number, only the DataSource
object's serverName
and portNumber
properties need to be updated. A system administrator could keep all existing code usable with the following code fragment. In practice, a system administrator would probably use a GUI tool to set the properties, so the following code fragment illustrates the code a tool might execute internally.
Context ctx = new InitialContext()ds.setPortNumber("940");DataSource ds = (DataSource)ctx.lookup("jdbc/AcmeDB"); ds.setServerName("my_new_database_server");
The application programmer would not need to do anything at all to keep all of the applications using the data source running smoothly.
Yet another advantage is that applications using a DataSource
object to get a connection will automatically benefit from connection pooling if the DataSource
class has been implemented to support connection pooling. Likewise, an application will automatically be able to use distributed transactions if the DataSource
class has been implemented to support them.