DataSource properties follow the JavaBeans design pattern, and therefore, the following getter/setter methods are in a DataSource object: public synchronized void setDatabaseNameString d
Trang 1An object-oriented programming language derives its strength from two areas First, you have the constructs of the programming language itself that allow you to write well-structured objects to extend that language Second, you have the extensive libraries of APIs that have been written to provide standard functionality Think for a moment about how APIs are created A software engineer does not just wake up one morning and have an entire API worked out in every detail Instead, an API's design is based on the experiences of professionals like you, who, over time, have gained insight through problem solving as to what is needed in an API to make it a useful part of developing an application Accordingly, over time, an API evolves through this community process to better fit the needs of the programming community
When it comes to the JDBC API, specifically the DriverManager facility, there is an evolution taking place In Chapter 4, we needed to put a significant amount of code around
DriverManager to implement a sharable connection facility It took even more work to make our sharable connections cacheable With the Java 2 Enterprise Edition (J2EE), a framework has been defined for sharing and caching connections This framework is the JDBC 2.0 Extension API In this chapter, we'll cover the JDBC 2.0 Extension API, which is a another set of JDBC interfaces, along with Oracle's implementation of these interfaces We'll also look at a functional caching object using Oracle's connection caching implementation Let's begin our journey through the new API with a look at the generic source for database connections, the DataSource class
7.1 DataSources
A DataSource object is a factory for database connections Oracle's implementations of data sources are database connection objects that encapsulate the registration of the appropriate database driver and the creation of a connection using predetermined parameters DataSource objects are typically bound with the Java Naming and Directory Interface (JNDI), so they can be allocated using a logical name at a centrally managed facility such as an LDAP directory
7.1.1 OracleDataSources
Oracle implements the DataSource interface with class OracleDataSource Table 7-1 lists the standard properties implemented by a DataSource object
Table 7-1 Standard DataSource properties
databaseName String Oracle SID
dataSourceName String Name of the underlying DataSource class
description String Description of the DataSource
networkProtocol String For OCI driver, determines the protocol used
password String Oracle password
portNumber int Oracle listener port number
serverName String DNS alias or TCP/IP address of the host
user String Oracle username
Trang 2DataSource properties follow the JavaBeans design pattern, and therefore, the following
getter/setter methods are in a DataSource object:
public synchronized void setDatabaseName(String databseName )
public synchronized String getDatabaseName( )
public synchronized void setDataSourceName(String dataSourceName) public synchronized String getDataSourceName( )
public synchronized void setDescription(String description)
public synchronized String getDescription( )
public synchronized void setNetworkProtocol(String networkProtocol) public synchronized String getNetworkProtocol( )
public synchronized void setPassword(String password)
public synchronized void setPortNumber(int portNumber)
public synchronized int getPortNumber( )
public synchronized void setServerName(String ServerName)
public synchronized String getServerName( )
public synchronized void setUser(String user)
public synchronized String getUser( )
The OracleDataSource class has an additional set of proprietary attributes These are listed in Table 7-2
Table 7-2 OracleDataSource properties
Property Data
driverType String
kprb for server-side internal connections oci8 for client-side OCI driver
thin for client- or server-side Thin driver
url String A convenience property incorporating properties, such as
PortNumber, user, and password, that make up a database URL tnsEntryName String TNS names address for use with the OCI driver
And these are the OracleDataSource property getter/setter methods:
public synchronized void setDriverType(String dt)
public synchronized String getDriverType( )
public synchronized void setURL(String url)
public synchronized String getURL( )
public synchronized void setTNSEntryName(String tns)
public synchronized String getTNSEntryName( )
Common sense prevails with these settings For example, there is no getPassword( ) method, because that would create a security problem In addition, the properties have a specific
precedence If you specify a url property, then any properties specified in the url override those that you specify by any of the other setter methods If you do not set the url property but instead specify the tnsEntryName property, then any related setter methods are overridden by the values in the TNS entry name's definition Likewise, if you are using the OCI driver and specify a network protocol of IPC, then any communication properties are ignored because the IPC protocol establishes a direct connection to the database Finally, a username and password passed in the getConnection( ) method override those specified in any other way Note that you must always specify a username and password with whatever means you choose
Trang 37.1.2 Getting a Connection from a DataSource
To get a connection from a DataSource use one of the two available getConnection( ) methods:
public Connection getConnection( )
throws SQLException
public Connection getConnection(String username, String password) throws SQLException
The first method creates a new Connection object with the username and password settings from the DataSource The second method overrides the username and password in the
DataSource
Now that you have an understanding of data sources, let's look at Example 7-1, which is an application to test the Thin driver using a DataSource
Example 7-1 An application using a DataSource to connect
import java.sql.*;
import oracle.jdbc.pool.*;
class TestThinDSApp {
public static void main (String args[])
throws ClassNotFoundException, SQLException {
// These settings are typically confi gured in JNDI,
// so they are implementation-specific
OracleDataSource ds = new OracleDataSource ( );
ds.setDriverType("thin");
ds.setServerName("dssw2k01");
ds.setPortNumber(1521);
ds.setDatabaseName("orcl"); // sid
ds.setUser("scott");
ds.setPassword("tiger");
Connection conn = ds.getConnection( );
Statement stmt = conn.createStatement( );
ResultSet rset = stmt.executeQuery(
"select 'Hello Thin driver data source tester '||" +
"initcap(USER)||'!' result from dual");
if (rset.next( ))
System.out.println(rset.getString(1));
rset.close( );
stmt.close( );
conn.close( );
}
}
First, our test application, TestThinDSApp, creates a new OracleDataSource object and then initializes its properties that are relevant to the Thin driver The OracleDataSource object implements the DataSource interface, so OracleDataSource is also considered to be a DataSource object Next, the program gets a connection from the DataSource using the getConnection( ) method Finally, just to prove everything is working OK, the application queries the database, and closes the connection
Trang 4So what have we accomplished using an OracleDataSource object? Recall that in Chapter 4
we established a connection using the following code:
Class.forName("oracle.jdbc.driver.OracleDriver");
Connection conn =
DriverManager.getConnection(
"jdbc:oracle:thin:@dssw2k01:1521:orcl","scott","tiger");
Now, using an OracleDataSource object, our code to establish a connection looks like: OracleDataSource ds = new OracleDataSource( );
ds.setDriverType("thin");
ds.setServerName("dssw2k01");
ds.setPortNumber(1521);
ds.setDatabaseName("orcl"); // sid
ds.setUser("scott");
ds.setPassword("tiger");
Connection conn = ds.getConnection( );
What's going on here? Our code is actually longer, which doesn't seem to improve things much, does it? But from another perspective, using a DataSource does represent an improvement, because a DataSource implements the Serializable interface, which means it can be bound using JNDI to a directory service What does that mean? It means we can define our connection parameters once, in one place, and use a logical name to get our connection from JNDI How does this help us? Let's say, for example, that we have 1,000 programs that use the specific connection parameters shown in Example 7-1 Let's further assume that we now have to move our database to another host If you wrote your programs using the DriverManager facility, you'll need to modify and compile all 1,000 programs However, if you used a DataSource bound to a directory using JNDI, then you need to change only one entry in the directory, and all the programs will use the new information
7.1.3 Using a JNDI DataSource
Let's take a look at a couple of sample applications that illustrate the power and utility of using data sources that are accessed via JNDI The examples use Sun's file-based JNDI
implementation You can download the class files for Sun's JNDI filesystem implementation at http://java.sun.com/products/jndi/index.html
First, the program in Example 7-2, TestDSBind, creates a logical entry in a JNDI directory to store our DataSource It uses Sun's JNDI filesystem implementation as the directory After that, we'll look at another program that uses the DataSource created by the first
My DataSource bind program, TestDSBind, starts by creating a Context variable named ctx Next, it creates a Properties object to use in initializing an initial context In layman's terms, that means it creates a reference to the point in the local filesystem where our program should store its bindings The program proceeds by creating an initial Context and storing its reference in ctx Next, it creates an OracleDataSource and initializes its properties Why an OracleDataSource and not a DataSource? You can't really use a DataSource for binding; you have to use an OracleDataSource, because the setter/getter methods for the properties are implementation- or vendor-specific and are not part of the DataSource interface Last, the program binds our OracleDataSource with the name joe by calling the Context.bind( ) method
Example 7-2 An application that binds a JNDI DataSource
import java.sql.*;
Trang 5import java.util.*;
import javax.naming.*;
import oracle.jdbc.pool.*;
public class TestDSBind {
public static void main (String args [])
throws SQLException, NamingException {
// For this to work you need to create the
// directory /JNDI/JDBC on your filesystem first
Context ctx = null;
try {
Properties prop = new Properties( );
prop.setProperty(
Context.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.fscontext.RefFSContextFactory");
prop.setProperty(
Context.PROVIDER_URL,
"file:/JNDI/JDBC");
ctx = new InitialContext(prop);
}
catch (NamingException ne) {
System.err.println(ne.getMessage( ));
}
OracleDataSource ds = new OracleDataSource( );
ds.setDriverType("thin");
ds.setServerName("dssw2k01");
ds.setPortNumber(1521);
ds.setDatabaseName("orcl");
ds.setUser("scott");
ds.setPassword("tiger");
ctx.bind("joe", ds);
}
}
Create a directory, JNDI, on your hard drive, and then create a subdirectory, JDBC, in your JNDI directory Compile Example 7-2 and execute it Assuming you get no error messages, you should find a bindings file in your new JDBC subdirectory This file holds the values for a
serialized form of your DataSource logically named joe This means that we can later retrieve a new connection by referencing a resource named joe
Now that we have a directory entry, let's test it with our next program, TestDSLookup, in
Example 7-3 First, TestDSLookUp creates an initial context just like TestDSBind did Next, it uses the Context.lookup( ) method to look up and instantiate a new DataSource from our serialized version of joe Finally, the program queries the database and closes the connection Pretty cool huh? When using DriverManager, you typically must specify the JDBC driver and database URL in your source code By using a DataSource together with JNDI, you can write code that is independent of a JDBC driver and of a database URL
Example 7-3 An application that uses a JNDI DataSource
import java.sql.*;
import javax.sql.*;
import javax.naming.*;
Trang 6import java.util.*;
public class TestDSLookUp {
public static void main (String[] args)
throws SQLException, NamingException {
Context ctx = null;
try {
Properties prop = new Properties( );
prop.setProperty(
Context.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.fscontext.RefFSContextFactory");
prop.setProperty(
Context.PROVIDER_URL,
"file:/JNDI/JDBC");
ctx = new InitialContext(prop);
}
catch (NamingException ne) {
System.err.println(ne.getMessage( ));
}
DataSource ds = (DataSource)ctx.lookup("joe");
Connection conn = ds.getConnection( );
Statement stmt = conn.createStatement( );
ResultSet rset = stmt.executeQuery(
"select 'Hello Thin driver data source tester '||" +
"initcap(USER)||'!' result from dual");
if (rset.next( ))
System.out.println(rset.getString(1));
rset.close( );
stmt.close( );
conn.close( );
}
}
7.1.4 Caveats
I hope you can appreciate the long-term gain of using DataSources with JNDI rather than embedding connections in your code:
• It makes your code independent of a JDBC driver
• It makes your code independent of a database URL
• It allows you to look up the driver and URL in one operation from anywhere on the network
DataSource objects do, however, have a few drawbacks One is that you can't use Oracle Advanced Security with the Thin driver, because there is no way to set the oracle.net
properties for data encryption and integrity This is because oracle.net properties are not a part of the standard, nor are they part of Oracle's implementation-specific set of DataSource properties Another drawback to using DataSources is that you have to make the investment in
an LDAP directory to truly leverage the use of JNDI, and that can be quite costly
In addition to the drawbacks I've mentioned, there are a few DataSource behaviors you should
be aware of One concerns the logging feature There are two methods you can use to set and
Trang 7get the log writer for a DataSource A log writer is a PrintWriter object used by the driver to write its activities to a log file They are:
public synchronized void setLogWriter(PrintWriter pw)
throws SQLException
public synchronized PrintWriter getLogWriter( )
throws SQLException
As with the DriverManager facility, logging is disabled by default You will always need to call the setLogWriter( ) method after a DataSource has been instantiated, even if you set the log writer before you bind it to a directory Why? Because the PrintWriter you specify in the setLogWriter( ) method is transient and therefore cannot be serialized A second behavior you should be aware of is that when DataSource logging is enabled, it bypasses
DriverManager's logging facility
There are also two methods you can use to set and get the login timeout, which is the amount of time that an idle connection should be kept open The methods are:
public synchronized void setLoginTimeout(int seconds) throws SQLException
public synchronized int getLoginTimeout( )
throws SQLException
Now that you have a firm grasp of how and when to use a DataSource object, let's continue our investigation of the JDBC 2.0 Extension API with a look at the connection pooling interface
ConnectionPoolDataSource
7.2 Oracle's Connection Cache
Recall that in Chapter 4 we talked about a cached pool of connections used by servlets When a servlet needed a connection, it drew one from the pool The servlet did its work, and when it was done, it returned the connection back to the pool The benefit of using cached connections is that
a servlet does not need to go through the resource-intensive task of opening a new database connection each time the servlet is invoked Also in Chapter 4, I showed a rudimentary
connection caching tool Rudimentary as it was, it still required a fair bit of rather complex code to implement As part of the JDBC 2.0 Extension API, Oracle provides a ready-made connection cache interface along with a sample implementation Instead of wasting your precious time doing something that has already been done for you, you can use Oracle's connection cache
immediately and in turn concentrate on the business problem at hand
At the heart of Oracle's connection caching framework is the connection pool data source It's important you understand what that is and how it works before getting into the details of the
connection cache framework itself
7.2.1 ConnectionPoolDataSources
A ConnectionPoolDataSource is a DataSource that can be pooled Instead of returning a Connection object as a DataSource object does, a ConnectionPoolDataSource returns a PooledConnection object A PooledConnection object itself holds a physical database
connection that is pooled In turn, a PooledConnection returns a Connection object This single layer of indirection allows a ConnectionPoolDataSource to manage
PooledConnection objects
You can use a PooledConnection to add or remove ConnectionEventListeners A
ConnectionEventListener is any Java program thread that wishes to be notified whenever a connection is opened or closed When a Connection is received from or returned to a
PooledConnection, the appropriate ConnectEvent event is triggered to close or return the
Trang 8Connection object to its associated pool In this case, the Connection object is not the same implementation of the Connection interface utilized by DriverManager Instead, it's a logical implementation managed by the PooledConnection object
The ConnectionPoolDataSource interface is implemented by the class
Oracle-ConnectionPoolDataSource, which extends OracleDataSource This means that all the methods from the OracleDataSource class and ConnectionPoolDataSource interface are available in an OracleConnectionPoolDataSource
The OraclePooledConnection class implements the PooledConnection interface and also provides the following five constructors:
public OraclePooledConnection( )
throws SQLException
public OraclePooledConnection(String url)
throws SQLException
public OraclePooledConnection(String url, String user, String passw ord) throws SQLException
public OraclePooledConnection(Connection pc)
public OraclePooledConnection(Connection pc, boolean autoCommit)
The OracleConnectionEventListener class implements the ConnectionEventListener interface It also provides the following two constructors and one additional method:
public OracleConnectionEventListener( )
public OracleConnectionEventListener(DataSource ds)
public void setDataSource(DataSource ds)
Collectively, these JDBC classes and interfaces, along with Oracle's implementation of them, provide a framework for connection caching However, the topic of how they can be used to build
a connection cache is well beyond the scope of this book Besides, Oracle already provides a connection cache interface and sample implementation Let's look at how you can leverage those
in your programs
7.2.2 Connection Cache Implementation
Let's start our discussion of Oracle's connection cache implementation by defining a few
important terms:
Connection pool
A pool of one or more Connections that use the same properties to establish a physical connection to a database By "properties," I mean things such as databaseName, serverName, portNumber, etc
Connection cache
A cache of one or more physical connections to one or more databases
Pooled connection cache
A cache of one or more connections to the same database for the same username Oracle's connection cache interface is named OracleConnectionCache Together, the
interface and its implementation provide a cache of physical connections to a particular database for a specified username
7.2.2.1 The OracleConnectionCache interface
Oracle's OracleConnectionCache interface defines the following three methods to aid you in managing a connection pool cache:
Trang 9public void close( )
throws SQLException
public void closePooledConnection(PooledConnection pc)
throws SQLException
public void reusePooledConnection(PooledConnection pc)
throws SQLException
These methods perform the following functions:
close( )
Used to close a logical connection to the database obtained from a
Pooled-Connection A logical connection is a connection that has been allocated from a pool When a logical connection is closed, the connection is simply returned to the pool It may physically remain open but is logically no longer in use
closePooledConnection( )
Used to remove the associated PooledConnection from a connection pool
reusePooledConnection( )
Used to return a PooledConnection to a connection pool
7.2.2.2 The OracleConnectionCacheImpl class
The OracleConnectionCacheImpl class extends OracleDataSource and implements the OracleConnectionCache interface Beyond what OracleConnectionCacheImpl inherits from OracleDataSource and the methods it implements from the OracleConnectionCache interface, the OracleConnectionCacheImpl class provides the following constants,
constructors, and methods:
public final int DYNAMIC_SCHEME
public final int FIXED_RETURN_NULL_SCHEME
public final int FIXED_WAIT_SCHEME
public OracleConnectionCacheImpl( )
throws SQLException
public OracleConnectionCacheImpl(ConnectionPoolDataSource cpds)
throws SQLException
public int getActiveSize( )
public int getCacheSize( )
public void setCacheScheme(int cacheScheme);
public int getCacheScheme( )
public void setConnectionPoolDataSource (ConnectionPoolDataSource cpds) throws SQLException
public void setMinLimit(int minCacheSize)
public int getMinLimit( )
public void setMaxLimit(int maxCacheSize)
public int getMaxLimit( )
The first three constants are used with the setCacheScheme( ) method to specify the caching scheme to be used by a given connection cache implementation Caches usually employ a minimum and maximum number of connections as part of a resource strategy The minimum value keeps a minimum number of connections on hand to speed up the connection process A cache uses the maximum value to limit the amount of operating-system resources utilized This prevents the cache from growing beyond its host's ability to provide resources The
setCacheScheme( ) method's constants control the behavior of the cache when the specified maximum connection limit has been exceeded The values are defined as follows:
DYNAMIC_SCHEME
Trang 10The cache will create connections above the specified maximum limit when necessary but will in turn close connections as they are returned to the cache until the number of connections is within the maximum limit Connections will never be cached above the maximum limit This is the default setting
FIXED_RETURN_NULL_SCHEME
The cache will return a null connection once the maximum connection limit has been exceeded
FIXED_WAIT_SCHEME
The cache will wait until there is a connection available and will then return it to the calling application
The OracleConnectionCacheImpl class implements two constructor methods, and there are three ways that you can use them to initialize a cache:
1 You can use the default constructor and set the connection properties individually after you've instantiated an object of the class
2 You can use the default constructor to instantiate an object of the class Then you can create a ConnectionPoolDataSource object, initialize it, and pass it as a parameter
to the setConnectionPoolDataSource( ) method
3 You can create and initialize a ConnectionPoolDataSource object and then pass it
as a parameter to the second form of the OracleConnectionCacheImpl constructor The other methods implemented by the OracleConnectionCacheImpl class are
straightforward getter and setter methods They do exactly what their names indicate
7.2.3 A Connection Caching Example
Now that you have an idea of what an OracleConnectionCacheImpl object can do, let's rewrite our caching object from Chapter 4 using Oracle's caching implementation We'll build a new caching object named OCCIConnection that will use the OracleConnectionCacheImpl class to create a modular caching module The overall development process that we'll follow for this example is:
1 We'll create a program that allows us to create a connection pool data source and bind it
to our JNDI directory
2 We'll create a class to implement and manage connection caches
3 We'll test the connection cache using one servlet that retrieves and uses connections and another that displays the current status of the cache
7.2.3.1 Creating and binding a ConnectionPoolDataSource
In my opinion, there's no advantage to using a DataSource unless you also utilize JNDI, so our examples here will once again use Sun's filesystem implementation of JNDI First, we'll create a program named OCCIBind, shown in Example 7-4, to bind a ConnectionPoolDataSource
to our JNDI directory OCCIBind is similar to the TestDSBind program shown in Example 7-2, but this time, we bind an OraclePoolConnectionSource
Example 7-4 An application that binds a ConnectionPoolDataSource
import java.sql.*;
import java.util.*;