Chapter 7. URL, DataSource, Connection, and Statements

This chapter describes how the Oracle JDBC supports URL, datasources, connections, and associated services, including connection wrapping, connection caching, connection scalability/load balancing, and connection fail-over, proxy connection. Finally, we’ll address JDBC statements (Statement, PreparedStatement, CallableStatement) and Oracle extensions, statement caching, and statement batching.

JDBC URL

A JDBC URL is the connect string used during database connection requests. It specifies the JDBC driver type, authentication information, the database instance, the host, the protocol, the port, service name, and so on. It contains either the complete authentication information or partial information plus indirections (i.e., aliases) to other mechanisms such as the TNSNAMES.ORA.

The Oracle JDBC URL format is as follows:

jdbc:oracle:<drivertype>:
[<username>/<password>]@<database_specifier>

where:

<drivertype> =  "thin" | "oci[1]" | "kprb"

[<username>/<password>] =  "<username>"/"<password>" | empty
<database_specifier> depends on driver type.
  • If <driver type> = “kprb,” then the database specifier is empty.

  • If <driver type> = “oci” and bequeath[2] connection, then the database specifier is empty (i.e., “jdbc:oracle:oci:scott/tiger/@”).

  • Otherwise, the database specifier description is one of the following:

    • <Thin style service name>

    • <host>:<port>:<SID>

    • <TNSName alias>

    • <LDAp Syntax>

Note that starting with the Oracle Database 10g JDBC, URL connect strings using SIDs are deprecated in favor of database service name:

jdbc:oracle:thin:@localhost:1521:orcl  -> deprecated
jdbc:oracle:thin:@localhost:1521/myservice ->
recommended

In summary, the <database specifier> is the variable part of the Oracle JDBC URL and may take one of the following formats:

<Oracle Net descriptor> |<Thin style service name> |
 <LDAP syntax>|<bequeath>|<TNSNames alias>

<Oracle Net descriptor> =
  • Thin:

    "jdbc:oracle:thin:@(DESCRIPTION=
               (LOAD_BALANCE=on)
                  (ADDRESS_LIST=
                  (ADDRESS=(PROTOCOL=TCP)(HOST=host1)(PORT=1521))
    
    (ADDRESS=(PROTOCOL=TCP)(HOST=host2)(PORT=1521)))
                   (CONNECT_DATA=(SERVICE_NAME=service_name)))"
  • OCI:

    "jdbc:oracle:oci:@(DESCRIPTION=
                (ADDRESS=(PROTOCOL=TCP)(HOST=cluster_alias)
    
                     (PORT=1521))
    
            (CONNECT_DATA=(SERVICE_NAME=service_name)))"
    
    <Thin style service name> =
    
    //host_name:port_number/service_name

    The full URL becomes jdbc:oracle:thin:[<user>/<password>]@// <host>[:<port>]/<service>.

    Examples:

    jdbc:oracle:thin:@//foo.com/customer
    jdbc:oracle:thin:scott/tiger@//mycomany.com:5521/sales
    
    <LDAP syntax> = "jdbc:oracle:thin:@ldap://
    ldap.acme.com:7777/sales,cn=OracleContext,dc=com"

    or:

    "jdbc:oracle:thin:@ldaps://ldap.acme.com:7777/
    sales,cn=OracleContext,dc=com"
    
    <TNSNames alias> =  String url =
    "jdbc:oracle:thin:@tns_alias";

    In 10.2, JDBC-Thin supports TNSNAMES entries lookup in tnsnames.ora, resulting in a much simplified and driver type–independent Oracle Net descriptor. You must set the system property oracle.net.tns_admin to the directory that contains your tnsnames.ora file.

    Example:

    java -Doracle.net.tns_admin=$ORACLE_HOME/network/admin

    or:

    System.setProperty("oracle.net.tns_admin", "c:oracle
    et
    admin");
    
    OracleDataSource ods = new OracleDataSource();
    ods.setTNSEntryName("tns_alias");
    
    ods.setUser("scott");
    ods.setPassword("tiger");
    ods.setDriverType("thin");
    Connection conn = ods.getConnection();

    Examples of valid URLs:

    • jdbc:oracle:thin:@mydatabase: The username and password must be provided by other mechanisms, while the following URL specifies an empty username and password.

    • jdbc:oracle:thin:/@mydatabase.

    • jdbc:oracle:thin:scott/tiger@//myhost:1521/orcl: connects user scott with password tiger to a database with service orcl through port 1521 of myhost, using the Thin driver.

    • jdbc:oracle:oci:@myhost:1521:orcl: connects to the database using the OCI driver and the SID orcl.

    • jdbc:oracle:thin:@HR: this URL connects to the database, whose alias in the tnsnames.ora file is HR, using the Thin driver. The username and password must be specifed by other means.

    See the Oracle JDBC Reference Guide and the Oracle Net Administration Guide documentation for more details on JDBC URL and TNSNAMES.ORA.

    Now that you have specified the JDBC URL, the next step consists of requesting a connection object from either the DriverManager or Data-Source factories. DriverManager is a deprecated mechanism maintained for backward compatibility purposes only, but it is still widely used. The next section explains DataSources.

DataSources

The javax.sql package introduced in JDBC 2.0 Optional Packages provides the following interfaces:

  • DataSource: javax.sql.DataSource

  • Connection pooling: PooledConnection, ConnectionPoolData-Source

  • Distributed transactions: XAConnection, XADataSource

  • Rowsets: RowSet, RowSetReader, RowSetWriter, RowSetMetaData, RowSetListener (JSR-114 specifies further JDBc RowSet)

This section focuses on the javax.sql.DataSource interface, which is a Connection factory; in other words, it returns a standard Connection object. It is a more flexible alternative to DriverManager, as it shields application developers from hard-coding database names and other datasource properties such as username and password.

Methods include: getConnection(), getConnection(java.lang.String username, java.lang.String password), getLoginTimeout(), getLogWriter(), setLoginTimeout(int seconds), and setLogWriter(java.io.PrintWriter out).

The OracleDataSource

The oracle.jdbc.pool.OracleDataSource class implements the javax.sql.DataSource interface with advanced connection caching extensions. Oracle’s extension includes the following methods:

getConnection(java.util.Properties
cachedConnectionAttributes),
getConnection(java.lang.String _user, java.lang.String
_passwd, java.util.Properties cachedConnectionAttributes),
getDriverType(), getServiceName(), getTNSEntryName().

Listing 7.1 illustrates a basic JDBC program using OracleDataSource; see the Oracle JDBC javadoc for a complete and detailed description.

Example 7.1. DataSrce.java

/*
 * Basic DataSrce sample
 */

import java.sql.*;
import javax.sql.*;

import oracle.jdbc.*;
import oracle.jdbc.pool.OracleDataSource;

public class DataSrce
{
  public static void main (String args [])
    throws SQLException
  {
    // Create an OracleDataSource
    OracleDataSource ods = new OracleDataSource();

    // Set the URL (user name, and password)
    String url = "jdbc:oracle:thin:scott/tiger@//localhost:1521/
orcl";
    ods.setURL(url);

    // Retrieve a connection
    Connection conn = ods.getConnection();

    // Create a Statement
    Statement stmt = conn.createStatement ();

    // Select the USER column from the dual table
    ResultSet rset = stmt.executeQuery ("select USER from dual");

    // Get and print the contains of the Result Set
    while (rset.next ())
      System.out.println ("User name is " + rset.getString (1));

    // Close the RseultSet, the Statement and the Connection
    rset.close();
    stmt.close();
    conn.close();
  }
}


C:My_DataPMBook>javac DataSrce.java

C:>java DataSrce
User name is SCOTT

Using SQL*Plus

SQL> select USER from dual;

USER
------------------------------
SCOTT

SQL>

DataSources and JNDI

JNDI 101

The Java Naming and Directory Interface (JNDI) specification provides Java applications with a standard naming service over directories or repositories for storing configuration parameters, objects, and object factories. This uniform directory and naming service allows applications to access different naming and directory services using a common API. The main interface is the javax.naming.Context, which furnishes (self-explanatory) methods such as the following:

  • bind(Name name, Object obj) / rebind(Name name, Object obj): for binding names to resources

  • unbind(Name name)

  • lookup(Name name, Object obj)

  • createSubcontext(Name name)

  • listBindings(Name name)

  • createSubcontext(...)

  • list (Name name)

The javax,naming.InitialContext class implements the Context interface; the InitialContext object is implementation specific and the starting point for JNDI operations.

The javax.naming.spi.ContextFactory interface is the factory for creating InitialContext objects.

Using Oracle DataSources with Stand-alone JNDI

When combined with DataSources, JNDI allows Java applications to access the database as a data source through its logical name as opposed to hard-coded or explicit URL. This has the advantage of making the application code independent of a particular database, JDBC driver, or JDBC URL and portable to other environments. In summary, the logical name is registered with a naming service; the application retrieves the DataSource object, looking up on the logical name previously registered (associated with it); and the application can then use the DataSource object to request a connection to the DataSource in question. JNDI implementation is in general furnished by middle tiers, typically J2EE containers of application servers,[3] but JNDI can also be used in J2SE (JDK) environments.

The following code sample describes how to use OracleDataSoure with a stand-alone JNDI provider (J2SE environment):

Step 1

Download a file system-based JNDI provider[4] (fscontext-1_2-beta3.zip).

Extract providerutil.jar and fscontext.jar and add the location to your classpath.

Create a directory with JNDI (e.g., C:TempJNDI).

Step 2

Create a Hashtable object for holding the parameter {name, value} pairs, necessary to access the naming service and create an Oracle-DataSource object.

Hashtable env = new Hashtable (5);
env.put (Context.INITIAL_CONTEXT_FACTORY,
          "com.sun.jndi.fscontext.RefFSContextFactory");
env.put (Context.PROVIDER_URL, "file:C:TempJNDI" );
// use the url supplied in command line or the
// following default
String url = "jdbc:oracle:oci8:@";
// import the Oracle database 10g JDBC pool package
import oracle.jdbc.pool.*;
OracleDataSource ods = new OracleDataSource();

Step 3

Set the name and value of parameters, including authentication and other datasource properties.

/*
 * Explicit parameters setting. It is also possible, with
 * JDKs, to load
 * parameter values from a file or XML document.
 */
 ods.setURL(url);
 ods.setUser("Scott");
 ods.setPassword("tiger");

Step 4

Obtain an initial JNDI naming context for storing the DataSource parameters in the naming space and bind the DataSource object to JNDI.

/*
 * Create an InitialContex object and bind the logical name
 * "MyDS" with
 * the previously created Oracle dataSource
 */
Context ctx = new IntialContext(env);
ctx.bind("MyDS", ods);

Step 5

Since the datasource object has been registered with JNDI, application code can retrieve it from the JNDI context.

ods =(OracleDataSource) ctx. lookup("MyDS");  // lookup
DataSource

Step 6

Get the JDBC connection object from the DataSource object factory:

Connection conn = ods.getConnection();

Here is a portable code sample, adapted from an Oracle JDBC code sample:

datasoure.properties
====================
url=jdbc:oracle:thin:scott/tiger@//localhost:1521/orcl

Example 7.2. DataSrceJNDI.java

/*
 *
 * Portable Application Using OracleDatasSource with JNDI
 */

import java.sql.*;
import javax.sql.*;
import oracle.jdbc.*;
import oracle.jdbc.pool.OracleDataSource;
import javax.naming.*;
import javax.naming.spi.*;
import java.util.Hashtable;
import java.util.*;
import java.io.*;

public class DataSrceJNDI
{
  public static void main (String args [])
    throws SQLException, NamingException, java.io.IOException
  {
    if ( args.length != 1 )
    {
       System.out.println("usage: java DataSrceJNDI <dir>");
       System.exit(0);
    }

    // Initialize the Context
    Context ctx = null;
    try {
      Hashtable env = new Hashtable (5);
      env.put (Context.INITIAL_CONTEXT_FACTORY,

              "com.sun.jndi.fscontext.RefFSContextFactory");
       env.put (Context.PROVIDER_URL, "file:" + args[0]);
       ctx = new InitialContext(env);
     } catch (NamingException ne)
     {
       ne.printStackTrace();
     }
     // Create a OracleDataSource instance explicitly
     OracleDataSource ods = new OracleDataSource();

    /*
     * Get URL from datasource.properties
     */

     InputStream is     = null;
     is =
 ClassLoader.getSystemResourceAsStream("datasource.properties"
 );
     Properties p = new Properties();
     p.load(is);
     is.close();
     String url = p.getProperty("url");
     System.out.println ("url in config file: " + url);
     ods.setURL(url);

     // Bind
     System.out.println ("Binding the logical name: jdbc/
 oracle");
     ctx.bind ("jdbc/oracle", ods);

     // Lookup
     System.out.println ("Looking with the logical name: jdbc/
 oracle");
     ods = (OracleDataSource) ctx.lookup ("jdbc/oracle");

     // Retrieve a connection
     Connection conn = ods.getConnection();

     // Create a Statement
     Statement stmt = conn.createStatement ();

     // Select the name of employee #7934 from EMP table
     ResultSet rset =
        stmt.executeQuery ("select ename from emp where empno
 ='7934'");

     // Iterate through the result and print the employee names
     while (rset.next ())
       System.out.println ("User name is " + rset.getString
(1));

     // Close the RseultSet
     rset.close();
     // Close the Statement
     stmt.close();
     // Close the connection
     conn.close();

  }
}

Compile and execute:

C:>javac DataSrceJNDI.java

C:>java DataSrceJNDI C:TempJNDI
url in config file: jdbc:oracle:thin:scott/tiger@//
localhost:1521/orcl
Binding the logical name: jdbc/oracle
Doing a lookup with the logical name: jdbc/oracle
User name is MILLER

C:>

Look at the contents of the .bindings file under the JNDI directory (i.e., C:TempJNDI).

After this overview of JDBC URL, OracleData sources with and without JNDI, the next step is to request a database connection. As we will see, there are many concepts and features around database connections.

Connections and Connection Services

Establishing a physical database connection is one of the most expensive database operations; sharing and reusing connections shields Java applications from paying such high cost, every time. This section describes the JDBC connection, as well as the services that the Oracle JDBC drivers furnish around it, and how these services help JDBC applications and Java/ J2EE components scale and be reliable when accessing either a single database instance or a cluster of database instances (i.e., RAC). The Oracle Database 10g JDBC drivers furnish a range of connection services on top of standard/basic connection pooling, including implicit connection caching, connection tagging, connection searching/retrieval, fast connection fail-over, runtime connection load balancing, connection proxy, and connection wrapping. Furthermore, all of these capabilities[5] are driver-type independent; in other words, these are available in both client-side drivers (i.e., JDBC-Thin and JDBC-OCI). Last, we’ll touch on JDBC and Grid computing. Grid computing is the buzzword du jour, a savant term for “resources consolidation and virtualization in order to reduce costs.” What does this have to do with JDBC? As we will see, there are new issues and requirements for Java/J2EE applications running against database grids.[6]

JDBC Connections and Oracle Extensions

The java.sql.Connection interface defines the JDBC Connection object, which is obtained from DataSources. The Connection object furnishes the following methods:[7] clearWarnings, close, commit, createStatement, createStatement, getAutoCommit, getCatalog, getHoldability, getMetaData, getTransactionIsolation, getTypeMap, getWarnings, isClosed, isReadOnly, nativeSQL, prepareCall, prepareStatement, releaseSavepoint, rollback, setAutoCommit, setCatalog, setHoldability, setReadOnly, setSavepoint, setTransactionIsolation, setTypeMap.

The oracle.jdbc.OracleConnection interface defines an Oracle JDBC Connection object (i.e., OracleConnection). It is Oracle’s extension to java.sql.Connection. The extensions include support for connection wrapping and services such as implicit connection cache, fast connection fail-over, and runtime connection load balancing, which are described later. In addition, it supports Oracle Statements objects, associated caching, and so on.

Methods such as _getPC(), applyConnectionAttributes(connAttr), close(connAttr), close(OracleConnection.INVALID_CONNECTION), getConnectionAttributes(), and so on have been added to support the extension features that will be described hereafter.

Connection Caching: Implicit Connection Cache

As already mentioned, establishing a physical database connection is one of the most expensive database operations. It involves creating and authenticating a database session through several message exchanges between the requester and the RDBMS. Connection pooling is a well-known technique for sharing a pool of physical database connections among a larger number of consumers, as illustrated by Figure 7.1. For example, a pool of 100 database connections can be satisfactorily shared by 1,000 concurrent users. As a result, the cost of connection establishment is eliminated. Java applications—including stand-alone JDBC applications, O/R mapping frameworks, Servlets, JavaServer Pages, Session Beans, BMP Beans, J2EE/EJB containers, and persistence frameworks—typically get preestablished connections from the pool. Connection pooling is transparent from an application’s viewpoint, although it traditionally involves calling a specific API (i.e., getPooledConnection()). The javax.sql.ConnectionPoolData-Source interface defines a factory for PooledConection objects (i.e., javax.sql.PooledConnection), which can participate in connection pooling (i.e., caching and reusing physical connections). As of Oracle JDBC 10g, an application simply invokes the DataSource.getConnection method to get a Pooled Connection object (i.e., javax.sql.PooledConnection) and uses it the same way it would use a physical Connection object (i.e., java.sql.Connection), hence the name Implicit Connection Cache. How does it work?

Connection Caching: the Big Picture

Figure 7.1. Connection Caching: the Big Picture

The Implicit Connection Cache is an extended JDBC-3.0-compliant[8]connection cache, which, beyond the standard, addresses some of the shortcomings in the JDBC connection cache specification, such as the requirement for all cached connections to belong to the same database, be authenticated by the same user, and the lack of a mechanism to either resize or refresh the cache or search for and reclaim abandoned connections.

As summarized in Table 7.1, the new connection cache in Oracle Database 10g JDBC, called Implicit Connection Cache has been designed to overcome limitations in previous implementations (i.e., OracleConnectionCacheImpl) by providing transparent access to the cache, support for multiple identities, and the ability to retrieve connections based on user-defined attributes and weights. In addition, it supports the ability to refresh or recycle stale connections from the cache. The base principle in Implicit Connection Cache is to only enable features explicitly requested by setting or changing connection cache properties and/or connection attributes. Middle-tier containers or frameworks such as the Oracle Application server, which integrates Implicit Connection Cache, allow enabling these features declaratively through deployment descriptors.

Table 7.1. Comparing Connection Caching in Oracle JDBC Drivers

Features

Pre-10g Connection Pooling

10g Connection Pooling

Transparent Cache Access

No

Yes

Refresh Stale Connections

No

Yes

Attributes-based Connection Retrieval

No

Yes

Reclaim and Reuse Abandoned Connections

No

Yes

Cache Heterogeneous Pairs of User/Password

No

Yes

Centralized Cache Management

No

Yes

Integration with RAC Events

No

Yes

Caching XA Connections

No

Yes (10.2)[*]

[*] Not exposed to end users.

Implicit Access to the Cache

By default, the getConnection() method in the standard DataSource API creates a new database session and a physical connection to a database, thus incurring performance and scalability penalties. With the Implicit Connection Cache, once the DataSource property ConnectionCachingEnabled has been set to true, getConnection() will service connection requests from the cache.

ods.setConnectionCachingEnabled(True);

public void setConnectionCacheProperties(java.util.Properties
cp)
     throws java.sql.SQLException

Where cp – is a list of key/value pairs properties. If a null value is set for a property, the default values will take effect.

Following is the list of furnished properties to configure and fine tune the behavior of your connection cache (the keys are case sensitive):

  • MinLimit: Specifes the low watermark or the minimum number of physical connections to remain in the cache. Defaults to 0.

  • MaxLimit: Specifies the high watermark or the maximum number of physical connections in the cache. Default to Integer.MAX_VALUE.

  • InitialLimit: Specifies the number of initial physical connections to be created when the cache is initialized. Defaults to 0.

  • MaxStatementsLimit: Sets the maximum number of statements that each physical connection keeps open in its cache. Defaults to 0.

  • InactivityTimeout: Sets the maximum time, in seconds, that a physical connection in the cache can remain idle (i.e., not checked out) before being closed. Defaults to 0 (i.e., disabled).

  • TimeToLiveTimeout: Sets the maximum time, in seconds, that a checked-out physical connection can remain idle. Defaults to 0 (i.e., disabled).

  • AbandonedConnectionTimeout: Sets the maximum inactivity time between two JDBC calls before regarding the connection as abandoned and reclaimed back to the cache. Note that in early implementation, the inactivity heartbeat starts at the beginning of a query but is not reset when the query completes so each operation must happen within the specified inactivity time since the begining of the previous one; check that you have the latest implementation or related patch. Defaults to 0 (i.e., disabled).

  • ConnectionWaitTimeout: Specifies the time limit to wait when a new connection request arrives and there are no connections to check out. Defaults to 0.

  • PropertyCheckInterval: A timeout daemon thread is associated with each cache to enforce the various timeouts listed previously; this parameter sets the time interval for the daemon thread to kick in and enforce timeout limits. Defaults to 900 seconds (15 minutes).

  • ValidateConnection: Verifies each physical connection for validity (by issuing a dummy query to the database) before a checked-out connection is returned to the caller. Defaults to false.

  • ClosestConnectionMatch: Causes the connection cache to retrieve the connection with the closest approximation to the specified connection attributes. Defaults to false.

  • AttributeWeights: Sets the weight (integer) for each connection attribute. This is used when ClosestConnectionMatch is set to true and enables retrieval of a connection with the highest combined weight of all its connection attributes. Defaults to a weight of 1.

  • LowerThresholdLimit: Sets the lower threshold limit on the cache. This is used when the releaseConnection() cache callback method is registered. Defaults to 20% (of MaxLimit).

The various timeout properties ensure that stale or abandoned connections are reclaimed back to the cache. However, to find out that a specific connection is invalid (i.e., dead server process or failed node), upon a SQL exception, the JDBC application must invoke isFatalConnectionError(java.sql.SQLException se). In the current implementation, the application has to explicitly let the Cache Manager know that a connection is invalid by closing it with INVALID_CONNECTION flag (i.e., myconn.close(INVALID_CONNECTION) ).

In addition, the Oracle JDBC drivers also furnish/support the following timeouts at TCP socket, SQL*Net, JDBC driver, and SQL Query levels:

  1. The following timeouts abort a hung query once the specified timeout has been reached (i.e., no data received from the server within the specified amount of time):

    // conn property – n seconds
    prop.put ("oracle.net.READ_TIMEOUT", n);
    
    // SQL Net Timeout -- n second
    prop.put (oracle.net.ns.SQLnetDef.TCP_CONNTIMEOUT_STR,
    "" + (n * 1000));
  2. sqlnet.recv_timeout: a new SQL*Net 10g parameter, which can be set on client and server sides in sqlnet.ora, as follows:

    # for 100 second timeout
    sqlnet.recv_timeout=100
  3. setQueryTimeout(): this is a standard JDBC timeout, which sets the number of seconds the driver will wait for a Statement to execute; it can be set at a SQL statement (Statement, PreparedStatement) level, as follows:

    stmt.setQueryTimeout(10);
  1. setLoginTimeout(): sets the maximum time in seconds that this datasource will wait while attempting to connect to a database:

    ods.setLoginTimeout(10) ;

The connection cache is created either explicitly using the Cache Manager API or implicitly by the first invocation of the getConnection() method. The cache is populated either by preinitializing it using the Cache Manager APIs or, incrementally, upon the release of connections back to the cache. The Cache Manager API, which is described in the next section, is not intended for use by JDBC applications but rather by middle-tier containers and frameworks.

Based on the following settings, the first call to getConnection() will create the cache MyCache, create a physical connection to the MyDS data-source, and the handle to the connection will be returned to the requester:

ods.setConnectionCacheName("MyCache"); // optional
ods.setConnectionCacheProperties(cp);  // optional
ctx.bind("MyDS", ods);
ods =(OracleDataSource) ctx.lookup("MyDS");  // lookup
// DataSource

// create a database session (physical connection)
   conn = ods.getConnection();

The following code snippet shows a skeleton of a complete JDBC program, which will create a MyCache cache and a connection associated to the SalesDS datasource, authenticated by “SCOTT”:

// look up the datasource object from the Context
   OracleDataSource ds = (OracleDataSource) ctx.lookup("SalesDB");
   java.util.Properties prop = new java.util.Properties ();
// setting the cache size to at least 2
   prop.setProperty("MinLimit", "2");

// create the cache and populate with 4 connections
   prop.setProperty("InitialLimit", "4");
   prop.setProperty("MaxStatementsLimit", "1");
   prop.setProperty("InactivityTimeout", "10");    // 10 seconds
   prop.setProperty("PropertyCheckInterval", "5"); // 5 seconds

// Setting cache properties on the datasource object
   ds.setConnectionCacheProperties (prop);

// 1st  invocation of getConnection creates the
// Cache with 4 connections
   Connection conn = ds.getConnection();

Subsequent invocations of getConnection() will simply retrieve available connections from the cache or create new ones (within the value of MaxLimit). Once the connection is retrieved, the requesting application can proceed with statement(s) creation:

// Create a Statement
    Statement stmt = conn.createStatement ();

...

// Do some work

...

// Close the Statement
    stmt.close();
    stmt = null;

Saving the Values of Connection Attributes

In order to make persistent the values assigned to the connection attributes for future use, JDBC applications may use one of two methods described in the following text.

Caching Heterogeneously Authenticated Connections

While a database does not impose any restriction on the connection authentication, a traditional cache might impose such a limitation on pairs of user/passwords. The Implicit Connection Cache can handle any user-authenticated connection. For example, the joe.blow connection can coexist very well with the bobo.lafleur connection in the same connection cache.

Connection Retrieval Based on User-Defined Attributes

One of the cool new features in the Implicit Connection Cache is the notion of connection striping. Connection striping or labeling consists of applying user-defined attributes to a connection and persisting the state of the attributes when the connection is returned back to the cache. These attributes can then be used later to retrieve the same connection from the cache; furthermore, cached connections do not have to restate attributes.

Retrieving a connection based on NLS_LANG attribute:

// a connection that matches with NLS_LANG attribute
   java.util.Properties connAttr = null;
   connAttr.setProperty("NLS_LANG", "ISO-LATIN-1");
   conn = ds.getConnection(connAttr);
...

Retrieving a connection based on isolation-level attribute:

// retrieve a connection that matches Transaction Isolation
   java.util.Properties connAttr = null;
   connAttr.setProperty("TRANSACTION_ISOLATION",
"SERIALIZABLE");
   conn = ds.getConnection(connAttr);
...
   conn.close(connAttr); // save settings for this connection

Retrieving a connection based on connection tag attribute:

...
java.util.Properties connAttr = null;
connAttr.setProperty("CONNECTION_TAG", "Rita'S_CONNECTION");
conn = ds.getConnection(connAttr); // retrieve Rita's
// connection
conn.close(connAttr); // Save connection_tag  for future
// retrieval
conn = ds.getConnection(connAttr); // retrieve Rita's
// connection

Applying Connection Attributes to a Cached Connection

A connection attribute can be applied to a cached connection using one of the following two approaches:

  1. Setting the attributes on the connection object by calling applyConnectionAttributes(java.util.properties connAttr. Connection attribute settings can be done incrementally/cumulatively through multiple calls. For example, the NLS_LANG attribute may be set in module M1, then module M2 sets the TXN_ISOLATION attribute, and so on.

    conn.applyConnectionAttributes(conAttr);
  2. Calling the close(java.util.properties connAttr) method on the connection object. This method closes the logical connection and then applies the connection attributes on the underlying physical connection (i.e., PooledConnection). Attributes setting via close() overrides other approaches.

    conn.close(connAttr); // apply the supplied
    // attributes to the connection object

Setting Unmatched Attribute Values

A JDBC application may set the values for the unmatched attributes before returning the connection to the cache. This ensures that this connection object will match subsequent connection requests with the same search attributes.

// Connection request
   java.util.properties connAttr = null;
   connAttr.setProperty("NLSLANG", "ISO-LATIN-1");
   conn = ds.getConnection(connAttr); // request connection
   java.util.properties unmatchedAttr =
                conn.getUnMatchedConnectionAttributes();
...
//Apply unmatched attributes before using the connection.
...

// Save the attributes settings
conn.close(connAttr);

Connection Retrieval Based on Attributes and Weights

Connections may be selectively retrieved from the connection cache based on a combination of ConnectionAttributes and attribute weights. Weights are assigned to each key in a ConnectionAttribute in a one-time operation that also changes cache properties. The cache property CacheAttributeWeights is a java.util.Properties that allows setting attribute weights. Each weight is a user-defined integer value that specifies how expensive the key is in terms of resources. Once the weights are set on the cache, connection requests are made on the DataSource by calling getConnection(connectionAttributes). The connectionAttributes argument refers to keys and their associated values. Retrieving the connection from the cache involves searching for a connection that satisfies a combination of the following:

  • A key/value match on a connection from the cache

  • The maximum total weight of all the keys of the connectionAt-tributes that were matched on the connection

    Here is an example using the CacheAttributeWeights property:

    // Define cache weights properties
        java.util.properties cacheProps = new Properties();
        java.util.properties cacheWeights = null;
    
        cacheWeights.setProperty("NLSLANG", "10");
        cacheWeights.setProperty("SecurityGroup", "8");
       cacheWeights.setProperty("Application", "4");
    ...
    // Apply the weight setting to the CacheAttributeWeights
    // property
       cacheProps.put(CacheAttributeWeights, cacheWeights);
    ...
    // Specify Connection Attributes
       java.util.properties connAttr = null;
       connAttr.setProperty("NLSLANG", "ISO-LATIN-1");
       connAttr.setProperty("SecurityGroup", "1");
       connAttr.setProperty("Application", "HR")
    
    //
       ds.setCacheName("MyCache");
    
    // First retrieval of the connection from myCache
       conn = ds.getConnection(connAttr);
    ...
    // Release the connection and Save attribute settings
       conn.close(connAttr);
    ...
    // Subsequent retrieval of the connection from the cache
       conn = ds.getConnection(connAttr);
    ...

    This getConnection() request tries to match and retrieve a connection from the MyCache cache. One of the following two things can happen:

    1. An exact match is found . As in the previous example, an exact match is a connection that satisfies the same attribute values and all the keys defined by Keys (NLS_LANG, SECURITY_GROUP).

      private void listProperties(java.util.Properties prop)
         {
          java.util.Enumeration enum = prop.propertyNames();
      
          String key = null;
          while ( enum.hasMoreElements() )
          {
              key = (String)enum.nextElement ();
              System.out.print(key + ": ");
              System.out.println (prop.getProperty(key));
          }
          System.out.print("
      ");
         }
      
      
         // List the matched properties
            connProp =
      
      ((OracleConnection)conn).getConnectionAttributes();
            System.out.println("
      The Properties of the
      connection:");
            listProperties (connProp);
    2. An exact match is not found. In this case, a closest match based on the attribute key/value and their associated weights are used (but only if the ClosestConnectionMatch property is set).

      // List the unmatched properties
            connProp =
      
      ((OracleConnection)conn).getUnMatchedConnectionAttribut
      es ();
            System.out.println("The Unmatched properties:");

      listProperties (connProp);

      For example, a closest match may be a connection that matches the attributes of NLS_LANG and APPLICATION but not SECURITY_GROUP. It is also possible to find connections that match some keys of the original list, but their combined weights are different. For example, connection1 could have a match of NLS_LANG with its associated weight of 10, whereas connection2 may have an attribute match of SECURITY_GROUP and APPLICATION with their combined weight of 12. In this case, it is desired that connection2 is returned. In other words, connection2 is the closest match and is more expensive to reconstruct (from the caller’s perspective) as opposed to connection1. When none of the connectionAttributes matches, a new connection is returned. The new connection is created using the user and password set on the DataSource.

      Once the connection is returned, the user can invoke the getUnMatchedConnectionAttributes() method on the connection object to get the list of attributes (java.util.Properties) that did not match the criteria. The caller can use the list of unmatched attributes to set the unmatched/missing values before using the connection.

List of System and Connection Properties

Driver.getPropertyInfo() // returns the list of all available properties. The following code fragment lists the driver and/or connection properties that the Oracle JDBC drivers support, as summarized in Table 7.2.

Table 7.2. Connection Properties (from the Oracle JDBC Users Guide)

Key

Value

Comment

User

String

The value of this property is used as the user name when connecting to the database.

password

String

The value of this property is used as the password when connecting to the database.

database

String

The value of this property is used as the SID of the database.

server

String

The value of this property is used as the host name of the database.

internal_logon

String

The value of this property is used as the user name when performing an internal logon. Usually this will be SYS or SYSDBA.

defaultRowPrefetch

int

The value of this property is used as the default number of rows to prefetch.

defaultExecuteBatch

int

The value of this property is used as the default batch size when using Oracle-style batching.

processEscapes

boolean

If the value of this property is “false,” then the default setting for Statement.setEscapeProccessing is false.

disableDefineColumnType

boolean

When this connection property has the value “true,” the method defineColumnType has no effect. This is highly recommended when using the Thin driver, especially when the database character set contains four-byte characters that expand to two UCS2 surrogate characters (e.g., AL32UTF8). The method defineColumnType provides no performance benefit (or any other benefit) when used with the 10.x.x Thin driver. This property is provided so that you do not have to remove the calls from your code. This is especially valuable if you use the same code with a Thin driver and either the OCI or Server Internal driver.

DMSName

String

Set the name of the DMS Noun that is the parent of all JDBC DMS metrics.

DMSType

String

Set the type of the DMS Noun that is the parent of all JDBC DMS metrics.

AccumulateBatchResult

boolean

When using Oracle-style batching, JDBC determines when to flush a batch to the database. If this property is “true,” then the number of modified rows accumulated across all batches is flushed from a single statement. The default is to count each batch separately.

oracle.jdbc.J2EE13Compliant

boolean

If the value of this property is “true,” JDBC uses strict compliance for some edge cases. In general, Oracle’s JDBC drivers will allow some operations that are not permitted in the strict interpretation of J2EE 1.3. Setting this property to “true” will cause those cases to throw SQLExceptions. This can be either a system property or a connection property. The default value of this property is “false” in classes12.jar and ojdbc14.jar. The default value is “true” in classes12dms.jar and ojdbc14dms.jar.

oracle.jdbc.TcpNoDelay

boolean

If the value of this property is “true,” the TCP_NODELAY property is set on the socket when using the Thin driver. See java.net.SocketOptions.TCP_NODELAY. This can be either a system property or a connection property.

defaultNChar

boolean

If the value of this property is “true,” the default mode for all character data columns will be NCHAR.

useFetchSizeWithLongColumn

boolean

If the value of this property is “true,” then JDBC will prefetch rows even though there is a LONG or LONG RAW column in the result. By default, JDBC fetches only one row at a time if there are LONG or LONG RAW columns in the result. Setting this property to true can improve performance but can also cause SQL Exceptions if the results are too big.

remarksReporting

boolean

If the value of this property is “true,” Oracle-DatabaseMetaData will include remarks in the metadata. This can result in a substantial reduction in performance.

includeSynonyms

boolean

If the value of this property is “true,” JDBC will include synonyms when getting information about a column.

restrictGetTables

boolean

If the value of this property is “true,” JDBC will return a more refined value for DatabaseMetaData.getTables. By default, JDBC will return things that are not accessible tables. These can be nontable objects or accessible synonymns for inaccessible tables. If this property is true, JDBC will return only accessible tables. This has a substantial performance penalty.

fixedString

boolean

If the value of this property is “true,” JDBC will use the FIXED CHAR semantic when setObject is called with a String argument. By default, JDBC uses VARCHAR semantics. The difference is in blank padding. With the default there is no blank padding, so, for example, “a” does not equal “a” in a CHAR(4). If true, these two will be equal.

oracle.jdbc.ocinativelibrary

String

Set the name of the native library for the oci driver. If not set, the default name, libocijdbcX (X is a version number), is used.

SetBigStringTryClob

boolean

Setting this property to “true” forces the Pre-paredStatement.setString method to use setStringForClob if the data is larger than 32,765 bytes. Please note that using this method with VARCHAR and LONG columns may cause large data to be truncated silently or cause other errors differing from the normal behavior of setString.

oracle.jdbc.StreamBufferSize

int

Set size of the buffer for the InputStream/ Reader obtained from getXXXStream/ getXXXReader. The default size is 16K. The size passed should be at least 4,096, or else 4,096 is assumed.

OCINewPassword

String

Pass the value of new password to be set during logon. This could typically be used for resetting the password when the password has expired or when the account is in the grace period.

oracle.jdbc.FreeMemoryOnEnter

ImplicitCache

boolean Clear the define buffers before caching the statement when Implicit statement caching is enabled. Setting the value to true would enable the clearing of define buffers before caching of Statements in the Statement cache. False is the default value, and this would behave in the same way as statement caching worked in prior releases.

oracle.jdbc.ReadTimeout

int

Read timeout while reading from the socket. This affects Thin driver only. Timeout is in milliseconds.

String url =
"jdbc:oracle:thin:@(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTO
COL=tcp)(HOST=<host>)(PORT=<port>)))(CONNECT_DATA=(SID=<sid>)
))";
DriverPropertyInfo[] dpi =
              new OracleDriver().getPropertyInfo(url,new
Properties());
for (int i=0; i<dpi.length; i++) {
 String dp_name = dpi[i].name; // get the name of property
 String value = dpi[i].value; // get the current value
  String dp_desc = dpi[i].description; // get the description
}

Implicit Connection Cache Code Sample

Listing 7.3 is a code sample taken from the Oracle JDBC code samples; it is made up of a set of code fragments that show how MinLimit, Inactivity timeout, TimeToLive timeout, AbandonedConnection timeout, and MaxStatement limit work.

Example 7.3. ImplicitCache.java

/*
 *  ImplicitCache.java
 */

import java.sql.*;
import javax.sql.*;
import java.util.*;
import javax.naming.*;
import javax.naming.spi.*;
import oracle.jdbc.*;
import oracle.jdbc.pool.*;

class ImplicitCache extends Thread
{

  Context ctx = null;

  public static void main (String args [])
       throws SQLException
  {
    ImplicitCache icc = new ImplicitCache();
    icc.start();
  }

  public void run ()
  {
    try
    {
      setods ();
      // See each procedure below for more details
      InactivityTimeout();
      TimeToLiveTimeout();
      AbandonedConnectionTimeout();
      MaxStatementsLimit();
    } catch (Exception e)
    {
      e.printStackTrace();
      System.exit(1);
    }
  }

/*
 * Show InactivityTimeOut
 */

  private void InactivityTimeout() throws Exception
  {
    Connection conns[] = new OracleConnection[8];
    int i = 0;
    ResultSet rs = null;
    Statement stmt = null;

  // look up the datasource object from the Context
    OracleDataSource ds = (OracleDataSource) ctx.lookup("SalesDS");

  // setting cache properties

  // set PropertyCheckInterval to a value lower than InactivityTimeout
  // to allow the deamon to kick-in during the inactivity period

     java.util.Properties prop = new java.util.Properties ();
     prop.setProperty("MinLimit", "2");     // low watermark is  2
     prop.setProperty("InitialLimit", "4"); // create cache with 4 conn
     prop.setProperty("InactivityTimeout", "10");    // 10 seconds
     prop.setProperty("PropertyCheckInterval", "5"); //  5 seconds
     ds.setConnection CacheProperties (prop);

  // Call getConnection to create the Cache
     Connection conn = ds.getConnection();
     conn.close();

     System.out.println("
*** Show Inactivity timeout ***");

     Thread.sleep(2000); // wait 2 second for the warming up
   // check the number of sessions after initialization
     System.out.println("The initial connections in the cache: "
        + checkNoSessions(ds)); // expect 4 as the InitialLimit

   // retrieve 8 connections, then check the number of
   // sessions.
      for ( i = 0; i < 8; ++i )
      {
        conns[i] = ds.getConnection();
      }
      System.out.println("expect 8 cached connections,  got:  "
           + checkNoSessions(ds)); // 8 is the number of connections

    // close all the connections, then check the number of
    // sessions to see whether it goes below the MinLimit
       for (i=0; i<8; ++i )
       {
         conns[i].close();
       }

    System.out.println("Sleep 15 sec to enable Inactivity Timeout");
    Thread.sleep(15000);
    System.out.println("number of cached connections "
         + checkNoSessions(ds)); // 1 = MinLimit - System's session

    for ( i = 0; i < 8; ++i )
    {
       if ( conns[i] != null )
       {
          conns[i].close();
          conns[i] = null;
        }
    }

    ds.close();// close the DataSource and clean the cache
  } // end of InactivityTimeout()


 /*
  * Show TimeToLiveTimeout
  */
  private void TimeToLiveTimeout () throws Exception
  {
    Connection conns[] = new Connection[15];
    Statement stmts[] = new Statement[15];
    int  i = 0;

    // look up the datasource object
    OracleDataSource ds = (OracleDataSource)ctx.lookup("SALESDB");

    // set cache properties
    java.util.Properties prop = new java.util.Properties();
    prop.setProperty("TimeToLiveTimeout", "10");     // 10 seconds
    prop.setProperty("PropertyCheckInterval", "5");  // 5 seconds
    ds.setConnectionCacheProperties(prop);

    System.out.println("
*** Show TimeToLive timeout ***");

    // create 15 connections and 15 statements
    System.out.println("Get 15 connections and statements from the cache");
    for ( i = 0; i < 15; ++i )
    {
       conns[i] = ds.getConnection();
       if ( conns[i] == null )
          System.err.println("conns[" + i + "] is bad");
       else

          stmts[i] = conns[i].createStatement();
    }

    // sleep 15 seconds longer than 10 sec TimeToLive
    // so as to enable TimeToLive timeout
    System.out.println("Sleeping 15 sec enables TimeToLive timeout");
    Thread.sleep (15000);

    // check all the statements and conn. should be claimed
      for ( i = 0; i < 15; ++i )
      {
       // check whether all those 15 stmts get canceled out
       try
       {
          ResultSet rs = stmts[i].executeQuery("select USER from DUAL");
          System.err.println("statement " + i + " still alive");
       } catch (Exception e)
       {
          System.out.print("Statement " + i + " is successfully canceled. ");
       }

       // check whether all those 15 conns get canceled out
       try
       {
         stmts[i] = conns[i].createStatement();
         System.err.println("connection " + i + "is still alive");
       } catch (Exception e)
       {
          System.out.println("connection " + i + " is successfully canceled");
       }
    }
    ds.close(); // close the datasource and clean up the cache
  } // end of TimeToLiveTimeout()


 /*
  * Show AbandonnedConnectionTimeout
  */
  private void AbandonedConnectionTimeout() throws Exception
  {
    Connection conns[] = new Connection[6];
    Statement  stmts[] = new Statement[6];
    int        i        = 0;
    int        j        = 0;

    // look up for the DataSource object
    OracleDataSource ds = (OracleDataSource) ctx.lookup("SALESDB");

    // set cache properties
    java.util.Properties prop = new java.util.Properties();
    prop.setProperty("AbandonedConnectionTimeout", "10");  // 10 seconds
    prop.setProperty("PropertyCheckInterval", "5");         //  5 seconds
    ds.setConnectionCacheProperties(prop);

    System.out.println("
*** Show AbandonedConnectionTimeout ***");

    // create 6 connections and 6 statements
    System.out.println("Get 6 conn. and statements from the cache");
    for ( i = 0; i < 6; ++i )
    {
       conns[i] = ds.getConnection();
       if ( conns[i] == null )
          System.err.println("conns[" + i + "] is null");
       else
          stmts[i] = conns[i].createStatement();
    }

    // Keep the first 3 connections active and let others inactive
    // and sleep 15 seconds to enable the AbandonedConnection timeout
    System.out.println("Let conns[0] ~ conns[2] be active, " +
                       "and others be inactive");
    for ( i = 0; i < 6; ++i )
    {
       for ( j = 0; j < 3; ++j )
       {
          stmts[j].execute("select USER from DUAL");
       }
       Thread.sleep (2500);  // each thread sleep 2.5 seconds
     }

    // check which connections are closed, which statements
    // are canceled, and which are still alive
    System.out.println("15 seconds later, conns[0] ~ conns[2] " +
                       "should still there");
    System.out.println("inactive conns[3] ~ conns[5] and their " +
                       "stmts should be canceled");
    for ( i = 0; i < 3; ++i )
    {
       try
       {
           ResultSet rs = stmts[i].executeQuery("select USER from DUAL");
           stmts[i].close();
       } catch (Exception e)
       {
          System.err.print("Statement handle " + i + " is canceled. ");
        }

        try
        {
           stmts[i] = conns[i].createStatement();
           System.out.println("conns[" + i +
                    "] and its statement handle is good");
        } catch (Exception e)
        {
           System.err.println("conns[" + i + "] is canceled");
        }
     }

     for ( i = 3; i < 6; ++i )
     {
        try
        {
           ResultSet rs = stmts[i].executeQuery("select USER from DUAL");
           System.err.println("Statement handle " + i + " is not canceled");
        } catch (Exception e)
        {
           System.out.print("statement handle " + i + "
            is successfully canceled. ");
        }

        try
        {
           stmts[i] = conns[i].createStatement();
          System.err.println("conns[" + i + "] is not canceled");
       } catch (Exception e)
       {
          System.out.println("conns[" + i + "] is successfully canceled");
       }
     }

     for ( i = 0; i < 6; ++i )
     {
        if ( conns[i] != null )
        {
          conns[i].close();
          conns[i] = null;
        }
     }
    ds.close(); // close the DataSource and clean up the cache
  } // end of AbandonedConnectionTimeout()
 /*
  * Show Statement caching
  */
  private void MaxStatementsLimit () throws Exception
  {
    Connection conns[] = new Connection[4];
    Statement  stmts[] = new Statement[8];
    int  i = 0;

    OracleDataSource ds = (OracleDataSource)ctx.lookup("SALESDB");

    // set the Datasource with maxium cache size as 4,
    // maximum statement size per connection as 1
    java.util.Properties cacheProp = new java.util.Properties();
    cacheProp.setProperty("MaxLimit", "4");
    cacheProp.setProperty("MaxStatementsLimit", "1");
    ds.setConnectionCacheProperties(cacheProp);

    System.out.println("
*** demo MaxStatementsLimit ***");
    for ( i = 0; i < 4; ++i )
    {
       conns[i] = ds.getConnection();
       if ( conns[i] == null )
          System.err.println("conns[" + i + "] is bad");
       else
       {
          System.out.print("conns[" + i +"].getStatementCacheSize(): "
             + ((OracleConnection)conns[i]).getStatementCacheSize());
          stmts[2*i] = conns[i].prepareStatement("select user from dual");
          ((PreparedStatement)stmts[2*i]).execute();
          stmts[2*i].close();

          stmts[2*i+1] = conns[i].prepareStatement("select user from dual");
          ((PreparedStatement)stmts[2*i+1]).execute();

          if ( stmts[2*i+1] == stmts[2*i] )
             System.out.println(". Get the cached statement for additional
request");
          else
             System.err.print("we get a different statement. ");
        }
     } // end of for loop
     for ( i = 0; i < 4; ++i )
     {
        if ( conns[i] != null )
        {
          conns[i].close();
          conns[i] = null;
        }
     }
     ds.close();
     ds = null;

  } // end of MaxStatementsLimit()

 /*
  * Utility to Create a JNDI-based Data Source
  */
  private void setods() throws Exception
  {
    // create a context for holding name-to-object bindings
    Hashtable env = new Hashtable (5);
    env.put (Context.INITIAL_CONTEXT_FACTORY,
           "com.sun.jndi.fscontext.RefFSContextFactory");
    env.put (Context.PROVIDER_URL, "file:./JNDI" );
    ctx = new InitialContext (env);
    // use the url supplied in command line or the default
    String url = "jdbc:oracle:thin:@";
    try {
      String url1 = System.getProperty("JDBC_URL");
      if (url1 != null)
        url = url1;
    } catch (Exception e) {
      // If there is any security exception, ignore it
      // and use the default
    }

    // create a DataSource
    OracleDataSource ods = new OracleDataSource();

    // set DataSource properties
    ods.setURL(url);
    ods.setUser("hr");
    ods.setPassword("hr");
    ods.setConnectionCachingEnabled(true);
    ods.setConnectionCacheName("ImplicitCache03");

    // set DataSource object to JNDI
    try {
       ctx.bind("SALESDB", ods);
    } catch (Exception e) { }
  } // end of setods()


 /*
  * Utility to check the number of active sessions
  * uses system account (and password)
  */
  private int checkNoSessions ( OracleDataSource ods)
  {
     Connection conn = null;
     PreparedStatement pstmt = null;
     ResultSet rs = null;
     int sessions = 0;

     try
     {
         conn = ods.getConnection("system", "manager");
         pstmt = conn.prepareStatement (
                      "SELECT COUNT(username) " +
                      "FROM v$session " +
                      "WHERE type != 'BACKGROUND'");
         rs = pstmt.executeQuery ();
         rs.next ();
         sessions = rs.getInt (1);
         // subtracting the entry of system/manager from the actual
         // entry
         --sessions;

         rs.close();
         rs = null;
         pstmt.close();
         pstmt = null;
         conn.close ();
         conn = null;
     } catch (SQLException e)
     {
         e.printStackTrace();
     }

     return sessions;
  } // end of private int checkNoSessions ()

}

The Connection Cache Manager

In order to centrally manage all caches associated with all datasources within a Java VM, the Oracle JDBC driver furnishes a Mister Cache Manager.

Managing and Maintaining Caches

The Oracle JDBC uses one instance of Connection Cache Manager per JVM to manage all of the connection caches used by applications running on that JVM. The Connection Cache Manager is responsible for creating caches, maintaining their states, and terminating them. Each cache is identified by a unique name and is tightly bound to a datasource. Each cache is created either transparently when getConnection() requests are made on a cache-enabled datasource or explicitly via the Connection Cache Manager API. Once a cache is created, it is terminated either explicitly via the Connection Cache Manager API or implicitly when the datasource is closed.

The Connection Cache Manager API

The Cache Manager API allows programmatic management of caches and access to information such as the number of connections checked out, the number of connections available in each cache, and so on. This API is intended for use by middle-tier containers or frameworks, not by the Java applications (however, when processing SQL exceptions, applications or containers should invoke the isFatalConnectionError() method. The oracle.jdbc.pool.OracleConnectionCacheManager javadoc provides the following methods:

  • createCache(OracleDataSource ods,java.util.Properties cacheProperties): Creates a connection cache using an auto-generated cache name and the supplied properties.

  • createCache(java.lang.String cacheName, OracleDataSource ods, java.util.Properties cacheProperties): Creates a connection cache using the supplied cache name and the properties.

  • disableCache(java.lang.String cacheName): Disables a given connection cache.

  • enableCache(java.lang.String cacheName): Activates a given connection cache.

  • existsCache(java.lang.String cacheName): Checks if a given connection cache exists.

  • getCacheNameList(): Gets all the cache names managed by this Connection Cache Manager; remember, there is one instance of Connection Cache Manager per Java VM (JDK).

  • getCacheProperties(java.lang.String cacheName): Gets the connection cache properties corresponding to the given cache.

    // list the connection cache properties
       System.out.println("
    Conn. Cache's properties ");
       listProperties (occm.getCacheProperties("MyCache"));
  • getConnectionCacheManagerInstance(): Returns the instance of ConnectionCacheManager.

  • getConnectionErrorCodes(): Retrieves pre-defined + user-added Fatal Error codes.

  • getNumberOfActiveConnections(java.lang.String cache-Name): Gets the number of connections already in use.

  • getNumberOfAvailableConnections(java.lang.String cacheName): Gets the number of connections in the cache that are available for use.

  • isFatalConnectionError(java.sql.SQLException se): Returns true if the SQL exception is one of ORA-3113, ORA-3114, ORA-1033, ORA-1034, ORA-1090, ORA-17008, plus user-defined fatal exceptions

  • purgeCache(java.lang.String cacheName, boolean clean-upCheckedOutConnections): Purges the connections in the cache (cleans the cache by removing connections).

  • refreshCache(java.lang.String cacheName, int mode): Refreshes the connections in the cache.

  • reinitializeCache(java.lang.String cacheName, java.util.Properties cacheProperties): Reinitializes a connection cache using the new supplied set of properties.

  • removeCache(java.lang.String cacheName, long waitTimeout): Removes the connection cache associated with the given name.

  • setConnectionErrorCodes(int[] fatalErrorCodes): Use this for adding user-defined exceptions to the list of fatal SQL Exceptions.

  • setConnectionPoolDataSource(java.lang.String cache-Name, javax.sql.ConnectionPoolDataSource ds): Sets the ConnectionPoolDataSource that may be used to create the implicit connection cache.

Connection Cache Manager Callback

The OracleConnectionCacheCallback interface lets the JDBC application control the handling of abandoned or released connections. It must be implemented by the JDBC applications and registered with an OracleConnection before checking out the connection from the cache. This section explains how a datasource layer or a stand-alone JDBC application can take avantage of the callback mechanism.

package oracle.jdbc.driver;
public interface OracleConnectionCacheCallback
{
    public boolean handleAbandonedConnection(OracleConnection conn,
                                                  Object userObject);
    public void releaseConnection(OracleConnection conn,
                                                  Object userObject);
}

// Callback implementation Skeleton
public class UserConnectionCacheCallback implements
OracleConnectionCacheCallback
{
     public boolean handleAbandonedConnection(OracleConnection conn,
        Object userObject)
     {
       ... implementation ...
     }

     public void releaseConnection(OracleConnection conn, Object
 userObject)
    {
         ... implementation ...
    }
}

The callback mechanism is expected to work as follows:

handleAbandonedConnection

If registered on an OracleConnection, it is invoked upon the expiration of the AbandonedConnectionTimeout, which it overrides, as follows:

  • Must return FALSE if the user wants to hold on to the connection longer; in this case the connection is not relaimed, it remains active.

  • Otherwise, it must return TRUE if the user wants to return the connection back to the cache (i.e., wants JDBC to reclaim this abandoned connection) but wants to handle the necessary cleanup and close the connection.

Note

JDBC returns a StaleConnectionException if the user attempts to use this connection later.

releaseConnection

By default, connections are released back to the cache by the JDBC user upon the invocation of conn.close(). The releaseConnection() callback method is used to override this default behavior, based on the following priorities set on the connection object:

  • OracleConnection.CONNECTION_RELEASE_LOCKED

  • OracleConnection.CONNECTION_RELEASE_LOW

  • OracleConnection.CONNECTION_RELEASE_HIGH

A setter and getter method is provided on the connection object to set the connection release priorities.

public void setConnectionReleasePriority(int priority) throws
java.sql.SQLException;

where priority takes one of the following value
    CONNECTION_RELEASE_LOCKED
    CONNECTION_RELEASE_LOW
    CONNECTION_RELEASE_HIGH

public int getReleasePriority();

If a releaseConnection() callback is registered on a connection, and the cache is empty, then:

  1. For every connection with a CONNECTION_RELEASE_HIGH priority, invoke the callback method.

  2. If the number of connections released back to the cache is below the MaxThresholdLimit (the default is 80 percent of MaxLimit):

    1. If all the connections with a CONNECTION_RELEASE_HIGH priority have been processed, then process the connections with CONNECTION_RELEASE_LOW priority.

    2. Stop releasing connections either when the cache is below the MaxThresholdLimit or when all the connections with CONNECTION_RELEASE_LOW and CONNECTION_RELEASE_HIGH priorities have been processed.

    3. Skip all connections that have the release priority set to CONNECTION_RELEASE_LOCKED.

registerConnectionCacheCallback

registerConnectionCacheCallback

registerConnectionCacheCallback(OracleConnectionCacheCallback
                  callbackImpl,  Object userObj, int callbackFlag)
-- callbackImpl user's implementation of the
                        OracleConnectionCacheCallback interface.
-- userObj any user object which is passed back, unmodified, when
                        the callback method is invoked.
-- callbackFlag  specifies which callback method should be invoked.
   It must be one of the following values:
   OracleConnection.ABANDONED_CONNECTION_CALLBACK - calls only
                   the  handleAbandonedConnection() method
   OracleConnection.RELEASE_CONNECTION_CALLBACK - calls only
                    the releaseConnection() method (see below)
   OracleConnection.ALL_CALLBACKS - calls all the connection cache
                    callback methods

For example, you can register a callback to invoke all callback methods, as follows:

((OracleConnection)conn).registerConnectionCacheCallback( new
UserConnectionCacheCallback(), new <userObject>,
                          OracleConnection.ALL_CALLBACKS);

conn.registerConnectionCacheCallback(new
UserConnectionCacheCallback(),
              this,  OracleConnection.ABANDONED_CONNECTION_CALLBACK);

Here is a code snippet for testing the AbandonedConnectionTimeout callback:

public void cachecallback(String dsName)
{
  // Look up the datasource object
  OracleDataSource ds = (OracleDataSource) ctx.lookup(dsName);

  // Set Cache properties
  java.util.Properties prop = new java.util.Properties();
  prop.setProperty("AbandonedConnectionTimeout", "4"); // 4 seconds
  prop.setProperty("PropertyCheckInterval", "1");

  // Apply the new properties to test the callback mechanism
  ds.setConnectionCacheProperties(prop);

  // get a connection from the cache
  OracleConnection conn = (OracleConnection) ds.getConnection();
  ds.getConnectionCacheProperties());

  public boolean abandonedFlag = false;
  public boolean ConnOk = false;

  conn.registerConnectionCacheCallback(new
UserConnectionCacheCallback(),
                  this,
OracleConnection.ABANDONED_CONNECTION_CALLBACK);

  System.out.println("sleep 8 sec to kick-in
AbandoonedConnectionTimeout");
  Thread.sleep(8000);
  try {
       Statement stmt = conn.createStatement();
       stmt.executeQuery("Select USER from dual");
       System.out.println("The connection is not abandoned");
       stmt.close();
  } catch (Exception e){
       System.out.println("The connection is abandoned");
       System.out.println(e.getMessage());
   }
}
class UserConnectionCacheCallback implements
                          OracleConnectionCacheCallback
{
   public boolean handleAbandonedConnection(OracleConnection conn,
                                                Object userObject)
   {
Statement stmt = null;
cachecallback snipet = (cachecallback) userObject;
snipet.abandonedFlag = true;
try {
stmt = conn.createStatement();
stmt.executeQuery("Select USER from dual");
stmt.close();
snipet.ConnOk = true;
       } catch (SQLException e) {
       snipet.ConnOk = false;
snipet.System.out.println("SQLException: " + e.getMessage());
       }

   if (snipet.abandonedFlag == true) {
    try {
conn.close();
   } catch (SQLException e) {
snipet.logErr(e);
   }
return true;
   } else
return false;

   } // end handleAbandonedConnection


    public void releaseConnection(OracleConnection conn,
                                           Object userObject)
    {
    cachecallback snipet = (cachecallback) userObject;
    String prioritySetting = "";
    prioritySetting = snipet.getPriority(conn);
    snipet.System.out.println("priority of connection is: " +
                                                 prioritySetting);
    try {
 snipet.System.out.println("Release connection with close()");
conn.close();
   } catch (SQLException e) {
snipet.logErr(e);
   }

  snipet.System.out.println("Exiting releaseConnection()");
   } // end releaseConnection

 }// end UserConnectionCacheCallback

}// end cachecallback

RAC Events and Fast Application Notification

When a new instance is added to a database cluster, when a new service is enabled on a database instance, or when an instance is retrenched from a cluster (i.e., instance stopped, service stopped, or node dies), the Oracle Database 10g RAC generates events that indicate what happened, including: SERVICE, SERVICE_MEMBER, DATABASE, INSTANCE, NODE, ASM, SRV_PRECONNECT, with the following possible status: UP, DOWN, NOT_RESTARTING, PRECONN_UP, PRECON_DOWN, and UNKNOWN.

The Oracle JDBC 10g drivers subscribe to the following RAC events and status:

  • Service Up: The connection pool starts establishing connections in small batches.

  • Instance (of Service) UpThe connection pool gradually releases idle connections associated with existing instances and reallocates these onto the new instance.

  • Instance (of Service) DownThe connections associated with the instance are aborted and cleaned up, leaving the connection pool with sound and valid connections.

  • Node DownThe connections associated with the instance are aborted and cleaned up, leaving the connection pool with good connections.

For reliability purposes, those events must be propagated to interested parties as fast as possible. Unfortunately, the timeout mechanisms (tcp_keepalive, tcp_ip_interval, and so on) are unreliable, because these may take a long (tens of minutes) to indefinite time to be kick-in. Fast Application Notification (FaN) is a new Oracle Database 10 g RAC feature that uses more reliable publish and subscribe notification mechanisms such as Oracle Streams Advanced Queue and the Oracle Notification Service (ONS), which detects and propagates quasi-instantaneously (few seconds) those events to components that have subscribed to these mechanisms. The Oracle JDBC subscribes to RAC events through the ONS mechanism. The following steps describe how to configure the Fast Application Notification through ONS.

  1. Set up a multinstance Oracle Database 10g RAC database (see RAC documentation).

  2. Virtualize the database host through a service name (see JDBC URL in section 7.1).

  3. Enable the Implicit Connection Cache.

  4. Configure ONS on each RAC server node.

  5. Configure ONS on each client node (10g Release 1) or use remote subscription (10g Release 2).

Step 4: Configure ONS on Each RAC Server Node

The onsctl utility lets you check that ONS is running on each RAC node:

$ onsctl ping
ONS Is Running

If you receive “ONS is running,” you can skip the rest of this step and go to step 5.

The easier approach for server-side configuration consists in using the RACGONS utility. The following command makes the RAC aware of your client/application tier:

racgons.bin add_config <hostname1>:<port> [<hostname2>:<port>
...]

Otherwise, here it goes.

The ONS configuration is controlled by the ONS configuration file, ORACLE_HOME/opmn/conf/ons.config. Three values should always be configured within ons.config:

  • Localport: Specifies the port that ONS binds to on the local host for talking to local clients:

    localport=4100
  • remoteport: Specifies the port that ONS binds to on all interfaces for talking to other ONS daemons:

    remoteport=4200
  • nodes: Specifies a comma-separated list (hostnames or IP_addresses:port) of other ONS daemons to talk to: a remote port that each ONS instance is listening on. In order to maintain an identical file on all nodes, the host:port of the current ONS node can also be listed in the nodes list. It will be ignored when reading the list. The list corresponds to the nodes in the RAC cluster. Listing the nodes ensures that the middle-tier node can communicate with the RAC nodes. There is no need to list every cluster and middle-tier node in the ONS config file on each RAC node. As long as one middle-tier node and one node in the RAC cluster are configured to see each other, all nodes are visible.

    nodes=myhost.example.com:4200,123.123.123.123:4200

    Three additional options—loglevel, logfile, and walletfile—can be provided in ons.config:

  • loglevel: Specifies the level of messages that should be logged by ONS. This value is an integer that ranges from 1 (least messages logged) to 9 (most messages logged, use only for debugging purposes). The default value is 3.

    loglevel=3
  • logfile: Specifies a log file for logging messages. The default value is $ORACLE_HOME/opmn/logs/ons.log.

    logfile=/private/oraclehome/opmn/logs/myons.log
  • walletfile: Specifies an Oracle wallet[9] file to be used to store SSL certificates. If a wallet file is specified to ONS, it will use SSL when communicating with other ONS instances and will require SSL certificate authentication from all ONS instances that try to connect to it. You cannot turn SSL on for one ONS instance independently. The wallet file should point to the directory where your ewallet.p12 file is located.

    walletfile=/private/oraclehome/opmn/conf/ssl.wlt/default
$ORACLE_HOME/opmn/conf/ons.config
localport=6100 # This is the port ONS is writing to
remoteport=6200 # This is the port ONS is listening on
loglevel=3
# This is the list of hosts and ports ONS is poting to.
# Include RAC and client nodes.
nodes=node-1.country.company.com:6200,node-
2.country.company.com:6200,
node-3.country.company.com:6200,node-4.country.company.com:6200,
node6-1.country.company.com:6200

Note

If RAC is installed on a shared file system, the $ORACLE_HOME/opmn/ conf directory might need to be created locally on each node.

For example, a symbolic link can be created to a directory under $HOME with the following UNIX commands, on each node:

$ mv $ORACLE_HOME/opmn/conf $ORACLE_HOME/opmn/conf.orig
$ mkdir $HOME/opmn/conf
$ cp ORACLE_HOME/opmn/conf.orig/* $HOME/opmn/conf
$ ln –s $HOME/opmn/conf $ORACLE_HOME/opmn/conf

With Oracle Database 10g Release 2, an additional option “useocr” simplifies the configuration file $ORACLE_HOME/opmn/conf/ons.config:

localport=6100    # This is the port ONS is writing to
remoteport=6200   # This is the port ONS is listening on
loglevel=3
useocr=on $ populate OCR

With useocr=on, the Cluster Ready Services (CRS) automatically populate the list of RAC nodes.

Step 5: Configuring/Subscribing ONS Clients

An ONSException is thrown at the first getConnection() if ONS is not correctly set up. The JVM in which your JDBC instance is running must have oracle.ons.oraclehome set to point to your ORACLE_HOME.

Here is how to configure ONS on client/application tiers:

  • If you are using the Oracle Application Server 10g, the OPMN process handles client registration for you; otherwise, proceed as follows:

  • If you are using Oracle Database 10g Release 2, the Oracle JDBC integrates the pure Java ONS client for remote subscriptions. The C-based client (10.1) is still delivered, though. Make sure that ons.jar is in CLASSPATH and then invoke setONSConfiguration(String remoteONSConfig) on an OracleDataSource where remoteONSConfig is a list of name/value pairs of the form name=value, separated by a new line character ( ).

    Here is an example of setting setONSConfiguration, without and with security wallet:

    setONSConfiguration("nodes=host1:port1,host2:port2 ");
    setONSConfiguration("nodes=host1:port1,host2:port2
    nwalletfile=wfile");

    See the Oracle Database 10.2 JDBC and RAC documentation for more details.

  • If you are using Oracle Database 10g Release 1, the ONS client is C-based and must be installed on every client (even if you are using JDBC-Thin). The ONS client libraries are included on the client CD. See the Oracle Database 10g RAC and JDBC documentation.

ONS is controlled through the onsctl utility located in ORACLE_HOME/ bin. This command-line utility accepts the following commands:

ORACLE_HOME/bin/onsctl start|stop|ping|reconfig|debug|help|detailed

These are described in Table 7.3.

Table 7.3. Command Descriptions

Command

Effect

Output

start

Starts the ONS daemon.

onsctl: ons started

stop

Stops the ONS daemon.

onsctl: shutting down ons daemon...

ping

Verifies whether the ONS daemon is running.

ons is running ...

reconfig

Triggers a reload of the ONS configuration without shutting down the ONS daemon.

 

debug

Displays debug information for the ONS daemon.

 

help

Prints a help summary message for onsctl.

 

detailed

Prints a detailed help message for onsctl.

 

Testing ONS Operations

The java class onc_subscriber.java is provided to test ONS functionality.[10] The class is an ONS client. It subscribes to the ONS daemon and listens for events.

/*
 * Copyright (c) 2001, 2004 by Oracle. All Rights Reserved
 * ONC Subscription client. This client listens for all events ONS receives
 * Based on the event type decisions are made on how and whether to print the
 * event body
 */

import oracle.ons.*;
import java.util.*;
import java.io.*;
import java.nio.*;

public class onc_subscriber
{

  public static void main(String args[])
  {
    boolean debug = false;
    // Set ONC-required System property for oracle.ons.oraclehome:
    //System.setProperty("oracle.ons.oraclehome", "<your ons home>");

    //Subscriber s = new Subscriber(""oraDb/myProdCluster"",
     "database/event/*");
    //Subscriber s = new Subscriber("", "database/event/*");
    Subscriber s = new Subscriber("", ""); // subscribe to all events
    Notification e;

    System.out.println("ONC subscriber starting");

    boolean shutdown = false;
    while (!shutdown)
    {
       e = s.receive(true);  // blocking wait for notification receive

       System.out.println( "** HA event received -- Printing header:" );
       e.print();
       System.out.println( "** Body length = " + e.body().length);
       System.out.println( "** Event type = " + e.type());

       /* Test the event type to attempt to determine the event body format.
          Database events generated by the racg code are "free-format" events -
          the event body is a string. It consists of space delimited key=value
          pairs. Events constructed using the ONC Java API have an event body
          that is a byte array (byte []) which have a terminating character.
          The Java API call to read the body waits on this character to
          terminate the read.
          Attempting to read a free-form event using the Java API can cause the
          reading thread to hang waiting on the byte array terminator.

          The following test only looks for database events, and events
          generated by the program onc_publisher.java. Other events will be

          received, but their bodies will not be displayed.
       */

        if (e.type().startsWith("database")) {
           if (debug) { System.out.println( "New print out"); }
           evtPrint myEvtPrint = new evtPrint(e);
        } else if (e.type().startsWith("javaAPI")){
           oncPrint myPrint = new oncPrint(e);
        } else {
          System.out.println("Unknown event type. Not displaying body");
        }

        try
        {
          if (e.type().equals("onc/shutdown")) {
             System.out.println("Shutdown event received.");
             shutdown = true;
          }
          else {
             java.lang.Thread.currentThread().sleep(100);
             System.out.println("Sleep and retry.");
          }
        }
        catch (Exception te)
        {
          te.printStackTrace();
        }
    }

    s.close();

    System.out.println(" ONC subscriber exiting!");
  }
}

Using JDK 1.4.2, edit the script Jmake_onc to reflect the JAVA_HOME containing JDK 1.4.2 and the ORACLE_HOME containing the client libraries:

  • Edit Jmake_onc

    Replace JHOME with a full path to JDK 1.4.2.

    Replace ORACLE_HOME with a full path to client libraries.

JHOME=/net/myclient/private/myuser/java/SUNWappserver/jdk/
export JHOME
ORACLE_HOME=/myclient/myuser/product/oracle/10gClient
export ORACLE_HOME
  • Save Jmake_onc

  • To build and run the classes, execute the following:

Step 1

$ Jmake_onc sub -c
>> Recompiling onc_subscriber ...
>> Executing onc_subscriber ...
ONC subscriber starting

Step 2

Perform an action on a RAC cluster that will cause an event to be generated. Stop an instance, start a service, start an instance, kill an instance, and so on.

Output

** HA event received -- Printing body:
Notification Type:         database/event/service
Affected Components:       null
Affected Nodes:            null
Delivery Time:             1095793164948
Generating Component:      oraDb/myProdCluster
Generating Node:           dlsun1344.us.oracle.com
Generating Process:        1095793164651
Notification ID:           109579316465111
Notification Creation Time:1095793164766
Cluster ID:                databaseClusterId
Cluster Name:              databaseClusterName
Instance ID:               databaseInstanceId
Instance Name:             databaseInstanceName
Local Only Flag:           FALSE
Cluster Only Flag:         FALSE
Body:                      [B@1888759

The correct generation and receipt of these events will demonstrate that ONS is configured correctly. It is possible to subscribe to and generate events from different machines, provided that the nodes list for both ONS daemons contains entries corresponding to the separate hosts. RAC events propagated to the ONS daemon being subscribed to by the onc_subscriber class will also be printed out.

High Availability: Fast Connection Failover

High availability is a major requirement in enterprise computing. This section will help you understand how JDBC 10g ensures high-availabilty for database connections. The unexpected node failure, planned database instance, or service shutdown in a clustered Oracle Database RAC environment generates a DOWN event, which automatically triggers a detect-and-fix reaction. Processing the event consists of quickly removing connections belonging to the failed instance and preventing invalid connections from being handed out on connection request. Oracle JDBC processes Instance, Service, and Node down events through the Fast Connection Fail-over (FCF) mechanism. FCF is implemented on top of the Implicit Connection Cache and works only in RAC environments. When a service/instance/ node goes down, a DOWN event is sent, which leads JDBC to clean up the cache from the orphan’s connections and redistribute the workload over surviving nodes only.

This section describes how Fast Connection Fail-over works, how it is enabled, and which steps the Java/JDBC developer must take to use it through the application.

How Fast Connection Fail-over Works

Each cached connection is mapped to a service, an instance, a database, and a hostname. Fast Connection Fail-over is bound to the OracleDataSource and the cache, but it cannot be enabled when reinitializing a connection cache and cannot be disabled as long as the cache lives.

When a RAC instance fails:

  • The database automatically rolls back in-flight (local) transactions; the applications do not have to do this.

  • The Fast Application Notification (FAN) mechanism (see ONS above) allows the rapid detection of “Instance DOWN” or “Node DOWN” events and propagates this event to the cache manager (event handler).

  • The event handler thread (one per JVM) and the Fast Connection Fail-over worker thread help the cache manager invalidate and remove all connections associated with the failed instance from the corresponding cache.

  • All connections/sessions and in-flight transactions associated with the failed instance will be lost.

  • The JDBC application realizes that an instance has failed when it receives a fatal SQL Exception (i.e., connectionCacheManager.isFatalConnectionError(e) returns “true”) upon an attempt to interact with the RDBMS. It must then retry to get a new connection (i.e., getConnection()), in order to be reconnected to another instance of the same database, and either replay the entire transaction or replay only from the last savepoint (if enabled). A J2EE container such as the Oracle Application Server Containers for J2EE (OC4J) may intersperse and transparently handle the retry/reconnection. Note: The Oracle Application Server Release 10.1.3 transparently handles the retry for CMP EJBs.

Configuring Fast Connection Fail-over

  1. Configure ONS and Fast Application Notification as described previously.

  2. Enable Fast Connection Fail-over.

  3. Code your application to catch SQLExceptions and retry connection requests.

Step 2: Enabling Fast Connection Fail-over

Once you have enabled Implicit Connection Cache on a datasource, you can enable Fast Connection Fail-over by setting the FastConnectionFailoverEnabled property to true, either through a datasource property object or by setting system properties (runtime flag) when launching the JDK VM.

Using DataSource Property (Programmatic):

// In this code snippet, OracleDataSource are bound to JNDI
// and cache properties are set on the datasource.

   import oracle.jdbc.pool.*;
   Context ctx = new IntialContext(ht);
   OracleDataSource ods = new OracleDataSource();

// Set Datasource properties
   ods.setUser("Scott");
   ods.setPassword("tiger");
   ods.setConnectionCacheName("MyCache");
   ods.setConnectionCacheProperties(cp);
   ods.setURL("jdbc:oracle:thin:@(DESCRIPTION= (LOAD_BALANCE=on)
  (CONNECT_DATA=(SERVICE_NAME=SalesDS)))");

// Enable Connection Caching First
   ods.setConnectionCachingEnabled(True);

// Enable Fast Connection Failover programmatically
   ods.setFastConnectionFailoverEnabled(true);
   ctx.bind("MyDS", ods);

...
// lookup datasource
   ds = lookup("MyDS");

// Create a Failover Enabled Connection Cache
   conn = ds.getConnection();

// do some work...

// return the connection to the cache
   conn.close();
...

// close datasource and cleanup the cache
   ods.close()

Using System Property (No Code Change):

Set the following system property:

java -D oracle.jdbc.FastConnectionFailover=true

Fast Connection Fail-over Status:

An application determines whether Fast Connection Fail-over is enabled by calling OracleDataSource.getFastConnectionFailoverEnabled(), which returns true if failover is enabled and false otherwise.

There is one cache per datasource per VM; in the absence of a middle-tier-container to enable FCF and create caches, you may have one special program (i.e., a Java Servlet) to do that. Alternatively, each bean may first invoke getConnectionFailoverEnabled() to check whether FCF has been enabled, so as to avoid enabling it several times (this won’t hurt though).

Step 3: Catching and Retrying Connection Request

The following code fragment shows how a JDBC application can detect node failure and retry getConnection().

// declare datasource
ods.setUrl("jdbc:oracle:oci:@(DESCRIPTION=
   (ADDRESS=(PROTOCOL=TCP)(HOST=cluster_alias)
   (PORT=1521))
   (CONNECT_DATA=(SERVICE_NAME=SalesDB)))");

ods.setUser("scott");
// Enable Implicit Connection Cache
ods.setConnectionCachingEnabled(true);
// Enable and Fast Connection FailOver
ods.setFastConnectionFailoverEnabled(true):
//  Bind and lookup the data source
ctx.bind("myDS",ods);
ds =(OracleDataSource) ctx.lookup("MyDS");
// The first getConnection will create the cache:
try {
     ods.getConnection();
     // do some work
} catch (SQLException e) {
    handleSQLException(e);
}
...
handleSQLException (SQLException e)
{
  if (connectionCacheManager.isFatalConnectionError(e))
      ConnRetry = true;  // Fatal Connection error detected
}

The following complete code sample loops over getConnection() in a RAC environment with FCF enabled. For the code to be effective, you have to generate instance UP and DOWN events.

FCFConnectionTest.java
======================

/*
 * FCFConnectionTest
 * Adapted from JDBC code sample with additional contributions
 *
 */
package FCFTest;
import java.io.IOException;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Properties;
import oracle.jdbc.pool.OracleDataSource;

public class Main  {
  public Main() {
    try {
      OracleDataSource ods = new OracleDataSource();
      String url = "jdbc:oracle:thin:@" +

"(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=sturachh1vip)(PORT=1521))"
+
      "(ADDRESS=(PROTOCOL=TCP)(HOST=sturachh2vip)(PORT=1521))" +
      "(LOAD_BALANCE=yes)" +
      "(CONNECT_DATA=(SERVICE_NAME=FCF)))";

      Boolean retryConnection = false;

      Properties prop = new Properties();
      prop.setProperty("MinLimit", "100");
      prop.setProperty("MaxLimit", "200");
      prop.setProperty("InitialLimit", "100");

      ods.setURL(url);
      ods.setUser("SCOTT");
      ods.setPassword("TIGER");
      ods.setConnectionCachingEnabled(true);

      ods.setConnectionCacheProperties(prop);
      ods.setFastConnectionFailoverEnabled(true);
      ods.setConnectionCacheName("ImplicitConnectionCache");

      SimpleDateFormat formatTime =
      (SimpleDateFormat)DateFormat.getTimeInstance(DateFormat.LONG);
      formatTime.applyPattern("dd.MM.yy HH:mm:ss");
      Connection conn = null;
      Statement stmt = null;
      ResultSet rset = null;
      long curTime = 0;
      long startTime = 0;

      for (int i=0; i<50; i++) {
        try {
          startTime = System.currentTimeMillis();
          System.out.println(formatTime.format(new Long(startTime))+
             ": Connecting with FCF Enabled" );
          if (retryConnection)
          {
           // This is a retry;
           // You migh want to do additional processing beforehand
            System.out.println ("getConnection retry");
           }
           conn = ods.getConnection();
           stmt = conn.createStatement();
           rset = stmt.executeQuery("select * from v$instance");
           while (rset.next()) {
             curTime = System.currentTimeMillis();
           System.out.println(formatTime.format(new Long(curTime))+":
 " +
             rset.getInt("INSTANCE_NUMBER") + " " +
 rset.getString("HOST_NAME"));
           }
           rset.close();
           stmt.close();
           conn.close();
           curTime = System.currentTimeMillis();
           System.out.println(formatTime.format(new Long(curTime))+
               ": Connection Closed");
           byte[] input = new byte[10];
           System.out.println("Enter to continue");

          System.in.read(input);
        } catch (SQLException sqle) {
            handleSE(sqle);
      }
    } catch (Exception e) {
      System.out.println("Caught an Exception" + e.getMessage());
      e.printStackTrace();
    }
  }

handleSE (SQLException e)
{
   e.getMessage();
   e.printStackTrace();
   if (connectionCacheManager.isFatalConnectionError(e)) {
      System.out.println("Caught a Fatal SQLException, must retry");
      retryConnection = true;  // Retry getConnection()
      Thread.sleep(30000);      // Wait few seconds before retry
}

/**
  *
  * @param args
  */
  public static void main(String[] args) {
    Main main = new Main();
  }
}

Scalability: Connection Load Balancing

Scaling up is also a key requirement in enterprise computing. Adding new database instances to an Oracle Database RAC cluster generates an UP event. The Oracle JDBC drivers proactively and automatically (i.e., without waiting for application connection retries/requests) spread the existing connections over all active RAC instances, including the newly added ones. Let’s consider a basic example where we have:

  • A database service SalesDB deployed over a two-node RAC cluster, one database instance per node.

  • An Oracle JDBC driver instance/node, part of a middle-tier container or stand-alone, holds a pool of 180 connections relative to the SalesDB service in two caches, hence 90 connections per datasource (instance).

Adding a new node to the service will trigger an UP event, which is detected and propagated via ONS. As a result, the connection cache manager will automatically split the pool of 180 connections over the three instances (or caches), resulting in 60 connections being established per database instance (or per cache). This process involves removing some connections from the former two caches and creating new ones to the new (third) cache.

Prior to Oracle Database 10g Release 2, load balancing was done using traditional load-balancing techniques, including random, round-robin, and FIFO load, which potentially lead to unbalanced workload distribution.

The Listerner config should contain:

PREFER_LEAST_LOADED_NODE_[listener]=OFF

Run-time connection load balancing is a new JDBC feature made possible by the new SERVICE_METRIC event from Oracle Database 10g RAC Release 2. The JDBC drivers susbcribe to service metrics advisory events through ONS and exploit these to achieve a well-balanced dynamic distribution of connection requests.

Service metrics are new RAC event types posted every 30 seconds or so and include the following:

  • A service flag, which indicates the state or health of the service or its metrics: GOOD (metrics are valid), UNKNOWN (no data for the service), VIOLATING (thresholds or event-level agreements violated), NO_DATA (no metric from this instance)

  • The percentage of work that should be directed to this service on this instance

  • Event retrieval and propagation to subscribers by various Oracle background processes, including MMON (retrieves and sends advice to AQ), IMON (retrieves and sends advice to ONS), EMON (retrieves and sends advice to OCI and ODP.NET clients), and PMON (retrieves and sends advice to Listener)

The routing of connection requests is based on flags and percentage idle; in addition, JDBC uses other algorithms (i.e., the Monte Carlo) for selecting the right connection cache. When a service comes up on an instance, or either newly added or rejoined after a crash, an UP event is sent, which leads JDBC to redistribute connections over the existing and the new instance. The redistribution of connections over nodes depends on the load-balancing goals.

See the Real Application Clusters Administration and Deployment Guide for more details on setting service goals.

The good news is that runtime connection load balancing is transparent to your JDBC application provided the RDBMS and the listener have been properly configured.

Enabling Runtime Connection Load Balancing

The following steps are required for enabling runtime connection load balancing:

Step 1

Set a goal for each service on the RDBMS side, using either dbms_service.create_service or dbms_service.modify_service, with one of the following values:

  • NONE: The default value uses the traditional load-balancing techniques for this service.

  • GOAL_SERVICE_TIME: Best response time overall, the load balancing is done based on weighted moving average elapsed time:

    DBMS_SERVICE.CREATE_SERVICE
    ('SERVICE1','SERVICE1.company.com',
        goal => DBMS_SERVICE.GOAL_SERVICE_TIME)
  • GOAL_THROUGHPUT: Best throughput overall, the load balancing is done using weighted moving average throughput.

    DBMS_SERVICE.CREATE_SERVICE
    ('SERVICE2','SERVICE2.company.com',
    
        goal => DBMS_SERVICE.GOAL_THROUGHPUT)

    Alternatively, the service goal can be set using the goal parameter in init.ora.

Step 2

Set a goal to the connection load balancing (CLB_GOAL) using either dbms_service.create_service or dbms_service.modify_service:

  • CLB_GOAL_SHORT: For applications that have short-lived connections (i.e., online shopping) or workloads where the rate of connections exceeds the number of connections to an instance.

    DBMS_SERVICE.MODIFY_SERVICE
    ('SERVICE1','SERVICE1.company.com',
        clb_goal => DBMS_SERVICE.CLB_GOAL_SHORT)
  • CLB_GOAL_LONG: For applications that have long-lived connections (i.e., connection pools, batch jobs) or workloads where the rate of work completion is equal to the rate new request.

    DBMS_SERVICE.MODIFY_SERVICE
    ('SERVICE2','SERVICE2.company.com',
        clb_goal => DBMS_SERVICE.CLB_GOAL_LONG)

    Alternatively, the connection listener’s load-balancing goal can be set using the clb_goal parameter in init.ora.

Step 3

Enable Fast Connection Fail-over either programmatically or using system property; the rest is transparent to the JDBC application. A Runtime Connection Load-Balancing (RCLB) Event Handler thread is started to listen to RCLB events.

JDBC Support for Transparent Application Fail-over

Transparent Application Fail-over (TAF) is an OCI feature (it should have been called “OCI fail-over”) that is exposed to Java through JDBC-OCI. Certain TAF features work well, but others are either misunderstood or do not work as expected.

What Usually Works with TAF

  • Database Connection Fail-over: TAF can fail over the physical connection either to a backup instance or to the same instance, transparently to the application. TAF can detect failures and reestablish failed sessions in a single-instance environment (on the same database) and in a RAC environment (see the preconnect option).

  • Query Resubmit or Query Failover: TAF can fail-over active cursors—in other words, SQL queries (prepared statements) that have begun to return results by reissuing the prepared statement (reopening the cursors) and discarding rows that have already been returned; then the application can continue fetching the remaining rows as if the failure never happened, at the price of performance drop, though.

What Does Not Work with TAF

  • TAF is an OCI8 feature; therefore, applications that are not using OCI8 libraries (i.e., via JDBC-OCI in Java case) cannot leverage it.

  • Rolling Back Active Transactions: If a failure occurs in the middle of a transaction, TAF requires that the application rolls back the transaction by issuing a ROLLBACK command, then lets TAF know by issuing an acknowledgment (that a rollback has been performed).

  • TAF does not protect or fail-over codes that have server-side states such as Java or PL/SQL packages; however, the application can register a callback function that will be called upon failure to reestablish the session states.

// register TAF callback function "cbk"
   ((OracleConnection) conn).registerTAFCallback(cbk,
msg);

TAF Configuration

TAF Configuration consists of specifying the following attributes of TNSNAMES.ORA:

  • Method = {BASIC | PRECONNECT} determines how fast fail-over occurs from the primary node to the backup node.

    BASIC: Establish connections at fail-over time only, in the mean time no work request is routed to the backup server.

    PRECONNECT: Preestablished connections eliminate the need to reconnect to a surviving instance upon failure and thus speed up fail-over. However, PRECONNECT pre-allocates resources awaiting the fail-over event, which requires the backup instance to be able to support all connections from every failed instance.

  • Recovery Type = {NONE|SESSION| SELECT}

    • NONE: Is the default; no fail-over functionality is used. This can also be explicitly specified to prevent fail-over from happening.

    • SESSION: The user session can reauthenticate; a new session is automatically created for the user on the backup node. This type of fail-over does not attempt to recover queries.

    • SELECT: Queries in progress by the time of the failure are reissued and processed from the beginning, discarding any rows that have already been returned to the application; the user session can continue fetching from the point of failure. However, this mode involves overhead on the client side in normal select operations.

  • BACKUP: If specified, the client will automatically connect to the backup node immediately; otherwise, the client will attempt to reconnect to the failed node and experience tcp_ip_abort_cinterval_delay. This is required when using PRECONNECT to preestablish connections.

  • DELAY: Number of seconds to wait between retries; defaults to one second if RETRIES is specified.

  • RETRIES: Number of retries; defaults to five attempts if DELAY is specified.

Examples of TNSNAMES.ORA entry; see Net8 Administrator’s Guide for more details.

primary=(DESCRIPTION=
           (ADDRESS=(PROTOCOL=tcp)(Host=hostname)(Port=1521))
              (CONNECT_DATA=(SERVICE_NAME=payrol)
                (FAILOVER_MODE=(TYPE=SELECT)(METHOD=BASIC))

Example of URL:

String url =
"jdbc:oracle:oci8:@(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=

"myhosy")(PORT="5521"))(CONNECT_DATA=(SID="ORCL")(FAILOVER_MO
DE=(TYPE=SELECT)(METHOD=BASIC)(RETRIES=10000)(DELAY=2))))";

Customizing TAF Using OCI Callback and Fail-over Events

TAF allows developers to register callback functions that can be used to reestablish session state after the failure.

CallBack  callbck = new CallBack();
...
// register TAF callback function
    ((OracleConnection) conn).registerTAFCallback(callbck,
msg);

The Java application captures the failure status and provides a function that the OCI callback mechanism will automatically call during fail-over. Here are the fail-over events defined in the OCI API:

  • FO_BEGIN = 1indicates a lost connection has been detected and fail-over is starting.

  • FO_END = 2indicates a successful completion of fail-over.

  • FO_ABORT = 3indicates an unsuccessful fail-over with no option of retrying.

  • FO_REAUTH = 4indicates that a user handle has been reauthenticated.

  • FO_ERROR = 5indicates that a fail-over was temporarily unsuccessful, but the application has the opportunity to handle the error and retry.

  • FO_RETRY = 6indicates retry fail-over.

  • FO_EVENT_UNKNOWN = 7indicates a bad/unknown fail-over event.

OCI Fail-over Interface

The OracleOCIFailover interface includes the callbackFn method, supporting the following types and events:

public interface OracleOCIFailover{

// Possible Failover Types
public static final int FO_SESSION = 1;
public static final int FO_SELECT  = 2;

public static final int FO_NONE  = 3;
public static final int;

// Possible Failover events registered with callback
public static final int FO_BEGIN   = 1;
public static final int FO_END     = 2;
public static final int FO_ABORT   = 3;
public static final int FO_REAUTH  = 4;
public static final int FO_ERROR  = 5;
public static final int FO_RETRY  = 6;
public static final int FO_EVENT_UNKNOWN = 7;

public int callbackFn (Connection conn,
                      Object ctxt, // Anything the user wants to save
                      int type, // One of the possible Failover Types
                       int event ); // One of the possible Failover
                                 // Events

class CallBack implements OracleOCIFailover {
  public int callbackFn (Connection conn, Object ctxt, int type, int
event) {

    String failover_type = null;

    switch (type) {
      case FO_SESSION:
           failover_type = "SESSION";
           break;
      case FO_SELECT:
           failover_type = "SELECT";
           break;
      default:
           failover_type = "NONE";
    }

    switch (event) {

      case FO_BEGIN:
           testCase.begin_called++;
           break;
      case FO_END:
           testCase.end_called++;

            break;
       case FO_ABORT:
            testCase.abort_called++;
            break;
      case  FO_REAUTH:
            testCase.reauth_called++;
            break;
       case FO_ERROR:
            testCase.error_called++;
            // Sleep for a while
            try {
              Thread.sleep(100);
            }
            catch (InterruptedException e) {
              System.out.println ("Thread.sleep Issue: " +
 e.toString());
            }
            return FO_RETRY;
       default:
            System.out.println (ctxt + ": Unknown failover event.");
            break;

    }
.}

Graceful Shutdown

For planned downtime, the DBA can configure TAF to transparently reconnect users to the backup instance as soon as the transactions complete, thereby achieving a graceful shutdown.

FCF versus TAF

Fast Connection Fail-over and TAF differ from each other in the following ways:

  • Application-Level Connection Retries: Fast Connection Fail-over supports application-level connection retries. You may see this as giving more control and flexibility to the application in how to process the fail-overs. In other words, with FCF, the application might choose to retry the connection or to rethrow the exception. TAF on the other hand retries connection transparently at the OCI/Net layer, out of the application’s control you may see this as a usability advantage.

  • Integration with the Connection Cache: Fast Connection Fail-over is integrated with the Implicit Connection Cache. Subsequently, failed connections are automatically invalidated. TAF, on the other hand, works on a per-connection basis at the network level; therefore, it currently does not notify the connection cache of failures.

  • Event-Based: Fast Connection Fail-over is based on the RAC event mechanism, so it detects failures quickly for both active and inactive connections. TAF, on the other hand, is based on the network call mechanism. Starting with Oracle Database 10g Release 2, TAF also subscribes to RAC events and Fast Application Notification, similar to JDBC FCF; however, TAF is not integrated with JDBC connection cache and does not furnish runtime connection load balancing. TAF can now send notifications to the OCI clients in the event of a failure to interrupt clients that are blocked waiting on a server acknowledgment.

  • Load Balancing: Fast Connection Fail-over and runtime connection load balancing support UP event load-balancing of connections and runtime work request distribution across active RAC instances.

  • Transaction Management: FCF automatically rolls back in-flight transations; TAF, on the other hand, requires the application to roll back the transaction and send an acknowledgment to TAF to proceed with the failover.

Proxy Authentication

In a multitier architecture, the end users typically authenticate and connect to a middle tier through various middle-tier authentication mechanisms. The middle tier in its turn authenticates against the database through various database authentication mechanisms. Proxy authentication is the ability for the middle tier to create a database session on its own behalf (i.e., mid-tier session) and then create a lightweight end-user session (i.e., client session) on behalf of the end user through the mid-tier session. The middle tier acts as a proxy to the client (end user), hence the name proxy session.

How Proxy Authentication Works

  1. Create middle-tier and client database schemas with appropriate database privileges. Be aware that granting coarse-grained roles such as “connect” and “resource” is not advised, so you should select which authorizations you’d like to grant and grant only these.

    create user midtier identified by midtier;
    grant create session, <+ selected privileges> to
    midtier;
    
    create user client identified by client;
    grant create session, <+ selected privileges> to
    client;
  2. By default, the middle tier cannot create connections on behalf of any client. The client must first allow it by granting “connect through” priveleges to the proxied used.

    ALTER USER client GRANT CONNECT THROUGH midtier;

    See the following different options.

  3. Specify the proxy properties that are passed to getConnection(). See the following JDBC. The end-user session is created through the proxy session (middle tier)—in reality the proxy session takes the identity of the end user—based on the following options:

    • USER_NAME: Supply only the username (default authentication) or username and password:

      ALTER USER client GRANT CONNECT THROUGH midtier;
      
      ALTER USER client GRANT CONNECT THROUGH midtier
      AUTHENTICATED USING password;
    • DISTINGUISHED_NAME: A global name in lieu of the user being proxied:

      CREATE USER client IDENTIFIED GLOBALLY AS
      
      'CN=client,OU=americas,O=oracle,L=redwoodshores,ST=ca,C
      =us';
      
      ALTER USER client GRANT CONNECT THROUGH midtier
      AUTHENTICATED USING
                  DISTINGUISHED NAME;
    • CERTIFICATE: Encrypted credentials of the user to be proxied:

      ALTER USER jeff GRANT CONNECT THROUGH scott
      AUTHENTICATED USING
                  CERTIFICATE;
    • ROLE: The following SQL command grants permission to the middle tier (the proxy session) to initiate a connection on behalf of the client, using the client’s database roles (i.e., proxyquery, proxyinsert, proxyupdate, proxydelete):

      Pre-Requisite
      
      Connect system/<password>
      create role proxyquery;
      create role proxyinsert;
      create role proxyupdate;
      create role proxydelete;
      
      grant select on tab1 to proxyquery;
      grant insert on tab1 to proxyinsert;
      grant update on tab1 to proxyupdate;
      grant delete on tab1 to proxydelete;
      
      grant proxyquery, proxyinsert, proxyupdate, proxydelete
      to client;
      
      commit;
      
      Then
      
      ALTER USER client GRANT CONNECT THROUGH midtier
           WITH ROLE proxyquery,proxyinsert, proxyupdate,
      proxydelete;

JDBC Support for Proxy Authentication

The Oracle JDBC drivers support proxy authentication in JDBC-Thin as of 10.1 and in JDBC-OCI as of 10.2, through the following methods.

public void openProxySession (int type,
java.util.Properties prop)
                      throws java.sql.SQLException

Opens a new proxy session with the username provided in the prop argument and switches to this new session where type can take one of the following values:

- OracleConnection.PROXYTYPE_USER_NAME  ->
   a java.lang.String object PROXY_USER_NAME needs to be set in prop.

- OracleConnection.PROXYTYPE_DISTINGUISHED_NAME  ->
   a java.lang.String object PROXY_DISTINGUISHED_NAME has to be set
   in prop.

- OracleConnection.PROXYTYPE_CERTIFICATE ->
   a bytep[] which contains the certificate PROXY_CERTIFICATE needs to be
   set in prop. Properties prop = new Properties();
   prop.put(OracleConnection.PROXY_CERTIFICATE, cert);
   oraConn.openProxySession(OracleConnection.PROXYTYPE_CERTIFICATE, prop);

- OracleConnection.PROXY_ROLES ->
   Roles can also be set in the property argument, the value is a String[]
   which contains the roles.
   prop.put(OracleConnection.PROXY_ROLES, roles);
   oraConn.openProxySession(OracleConnection.PROXY_ROLES, prop);


public boolean isProxySession(): returns true if the session
   associated with this connection is a proxy session.

public void close(int OracleConnection.PROXY_SESSION) throws
  java.sql.SQLException closes the proxy session.

The following code sample illustrates Proxy Authentication:

ProxyAuten.java
===============
/*
 * Pre-Requisite: create the database users (client, proxy)
 * or use existing database schemas: HR (client), and SCOTT (proxy)
 * See "How proxy Authentication Works"
 *
 * connect system/<password>
 * create role proxyquery;
 * create role proxyinsert;
 * create role proxyupdate;
 * create role proxydelete;
 * grant select on regions to proxyquery;
 * grant insert on regions to proxyinsert;
 *      grant proxyquery, proxyinsert to hr;
 *      ALTER USER hr GRANT CONNECT THROUGH scott WITH ROLE
 *              proxyquery,proxyinsert;
 *
 */

import java.util.*;
import java.sql.*;
import oracle.jdbc.OracleConnection;
import oracle.jdbc.pool.OracleDataSource;

class ProxyAuthen
{
  public static void main (String args [])
       throws SQLException
  {
    OracleConnection conn = null;
    Statement stmt = null;
    ResultSet rset = null;
    OracleDataSource ods = new OracleDataSource();
    String url = "jdbc:oracle:thin:scott/tiger@//localhost:1521/
orcl";
    ods.setURL(url);

    // get a proxy connection
    conn = (OracleConnection)ods.getConnection();

    // Check if the connecton is a proxy session, should return false

    System.out.println("isProxySession? " + conn.isProxySession());


    // open a proxy session of type "connect through with role".
    Properties prop = new Properties();
    prop.put(OracleConnection.PROXY_USER_NAME, "HR");
    String[] roles = {"proxyquery", "proxyinsert"};
    prop.put(OracleConnection.PROXY_ROLES, roles);
    conn.openProxySession(OracleConnection.PROXYTYPE_USER_NAME,
prop);

    // Check if the connecton is a proxy session, should return true
   System.out.println("isProxySession? " + conn.isProxySession());


    // Check the Current Id of the Session
    stmt = conn.createStatement();
    rset = stmt.executeQuery("select user from dual");
    while (rset.next())
     {
         System.out.println("This Session is associated with: " +
         rset.getString(1));
     }
     rset.close();

     // Performing authorized activities on behalf of the client
     rset = stmt.executeQuery("select count(*) from regions");
     while (rset.next())
     {
      System.out.println("# of existing Regions: " + rset.getInt(1));
     }
     rset.close();

     System.out.println("Adding a new Region and Printing the whole
table");
     stmt.execute("insert into regions values (5, 'New Region')");

     rset = stmt.executeQuery("select * from regions");
     while (rset.next())
     {
       System.out.println("Region id: " + rset.getInt(1) + "  Region
Name: "

         + rset.getString(2));
      }
      rset.close();

     // Attempt to perform unauthorized activities/roles must fail

      try
      {
       stmt.execute("delete from regions where region_id = 1");

      } catch (SQLException e)
      {
        System.out.println("Proxy user attempts to delete from
HR.Regions");
     }
     stmt.close();
     conn.close();
     ods.close();

  }
}

C:>javac ProxyAuthen.java

C:>java ProxyAuthen
isProxySession? false
isProxySession? true
This Session is associated with: HR
# of existing Regions: 4
Adding a new Region and Printing the whole table
Region id: 1 Region Name: Europe
Region id: 2 Region Name: Americas
Region id: 3 Region Name: Asia
Region id: 4 Region Name: Middle East and Africa
Region id: 5 Region Name: New Region
Proxy user attempts to delete from HR.Regions

C:>

Caching Proxy Sessions

...

java.util.Properties connAttr = null;
...
//obtain connection from a cache enabled DataSource
OracleConnection conn = ds.getConnection("scott",tiger");
conn.openProxySession(proxyType, proxyProps);
...
connAttr.setProperty("CONNECTION_TAG","JOE'S_PROXY_CONNECTION");
conn.applyConnectionAttributes(connAttr); //apply attributes to the
// connection
conn.close(); //return the tagged connection to the cache
...
//come back later and ask for Joe's proxy connection
conn=ds.getConnection(connAttr); //This will retrieve Joe's proxy
// connection

Connection Wrapping

Wrapping a Java class is a common object-oriented programming technique, which consists of creating entirely new classes that implement the same interfaces as the original classes. In general, the wrapped classes forward to the original classes, but special codes can be added to the former. J2EE containers use this technique to intercept JDBC calls and intersperse transaction management, exception handling, connection management, performance enhancements, and logging services on top of the original conection object. Since Oracle 9i Release 2, the Oracle JDBC drivers support connection wrapping through the oracle.jdbc.OracleConnectionWrapper class, which implements java.sql.Connection and OracleConnection.

// Wrap
Connection conn = ds.getConnection();
Connection w = new OracleConnectionWrapper(conn);

// Unwrap
Connection orig = ((OracleConnection)w).unwrap

The following example is adapted from the ConnectionWrapperSample.java in Oracle JDBC demos:

ConnWrapper.java
================
/*

 * Demo wrapping and  unwrapping a JDBC  connection
 */

import java.sql.*;
import oracle.jdbc.*;
import oracle.jdbc.pool.OracleDataSource;

public class ConnWrapper
{
  public static void main (String args [])
       throws SQLException
  {
    // Create an OracleDataSource
    OracleDataSource ods = new OracleDataSource();

    // Set the URL, user name, and password
    String url = "jdbc:oracle:thin:scott/tiger@//localhost:1521/
orcl";
    ods.setURL(url);
    OracleConnection conn = (OracleConnection)ods.getConnection();

    // Wrap the connection
    OracleConnectionWrapper wrappedConn =
        new OracleConnectionWrapper( conn );
    System.out.println(" **** Wrapped Connection ***");

    // Print employee names
    Statement stmt = wrappedConn.createStatement ();
    ResultSet rset = stmt.executeQuery ("select ENAME from EMP");

    while (rset.next ())
      System.out.print(rset.getString(1) + " ");
    rset.close();
    stmt.close();
    System.out.println();

   // Get a standard connection by unwrapping the wrapped Connection

    OracleConnection orig = ((OracleConnection)wrappedConn).unwrap();
    System.out.println(" **** UnWrapped Connection ***");

    // Print employee names

    stmt = orig.createStatement ();
    rset = stmt.executeQuery ("select ENAME from EMP");
    while (rset.next ())
      System.out.print(rset.getString(1) + " ");
    rset.close();
    stmt.close();
    System.out.println();

    conn.close();
  }
}

C:>javac ConnWrapper.java

C:>java ConnWrapper
 **** Wrapped Connection ***
SMITH ALLEN WARD JONES MARTIN BLAKE CLARK SCOTT KING TURNER ADAMS
JAMES FORD MILLER
 **** UnWrapped Connection ***
SMITH ALLEN WARD JONES MARTIN BLAKE CLARK SCOTT KING TURNER ADAMS
JAMES FORD MILLER

C:>

Assume you need to augment/tweak getXXX() in ResultSet across all your JDBC applications. You can generalize the wrapper technique to OracleDataSource, OracleConnection, OracleStatement, and OracleResultSet, so that the new behavior is implemented at the driver level and not across your applications. Each of these classes and interfaces may have an instance variable, which stores an instance implementing the corresponding interface and a constructor that lets you create the wrapped instance by passing in the wrapper.

JDBC Connections in Grid Environment

Unless you have been living in a cave the last two years, you probably have heard about the grid. Leaving aside the marketing hype and whatever it is called (i.e., utility computing, on-demand computing, autonomic computing), grid computing aims at substantially cutting costs by consolidating IT resources, virtualizing their location, allocating/provisioning, and managing resources based on policies. Why should you care about the grid at the JDBC level? Cost cutting is a compelling value proposition galvanized by a new generation of blade servers and network-attached storage; as such, all layers of enterprise computing must adapt and support these new platforms. In principle, application servers and their containers, such as the Oracle Application Server, shield Java/J2EE developers from the direct impact of grid platform. If you’ve read this book so far, you are already aware of the mechanisms in Oracle JDBC that address these requirements. To summarize:

  • Most JDBC applications use “connect strings” that explicitly specify the hostname or IP address of the target database. This won’t work in enterprise grid computing, where nodes are dynamically added to or retrenched from the cluster of database servers. The solution consists of virtualizing the database host and instance by connecting to a service and not to an actual physical server; as a result, clients (i.e., JDBC applications, middle tier, and so on) are insulated from changes made to the physical elements of the cluster (add/remove nodes), as well as from node failures.

  • Caching only identically authenticated connections is useless in an enterprise grid environment, where you need to serve thousands of arbitrarily authenticated customers. The solution here is the ability to cache heterogeneously authenticated connections and proxy connections.

  • Application servers (and J2EE containers) that rely on unpredictable timeout mechanisms will not be able to provide a reliable, guaranteed service in an enterprise grid environment. The solution is an event-based mechanism, which can notify susbscribers as the event happens, so that appropriate action can be taken in a timely manner. The Oracle Notification Services do just that by notifying the Oracle JDBC drivers of RAC events (e.g., Instance Up, Instance Down, Node) instantaneously, and processing these so as to provide a fast and reliable fail-over and connection load balancing.

  • In order to manage and service database resources on demand, a mechanism for dynamically creating, resizing, and terminating connection caches is required.

  • A feature-rich connection cache, which lets you search, retrieve, reclaim, and allocate connections based on user-defined criteria.

Virtualizing the Database as a Service

Grid computing materializes itself at every level of computing infrastructure, including the storage grid, disk and network grid, database grid, and application server grid. A grid of Oracle databases is made of a set of clustered databases (RAC), in which each clustered database is hosted on shared networked storage and concurrently accessed and managed by multiple database server instances. The traditional Oracle datasource URL attribute or connect string is in the form of the following:jdbc:oracle<type>@<host>:<port>:<sid>

String connString="jdbc:oracle:thin:@prodHost:1521:ORCL";

In an Oracle Database RAC environment, the hosts and database server instances that make up the cluster are represented as a single logical entity known as a service. A client application connects to the logical database service without any real knowledge of the actual host or database server instance that’s used in the cluster. By connecting to the service, and not to an actual physical server, clients can be insulated from changes made to the physical elements of the cluster, as well as insulated from node failures.

As we have seen, in the Oracle Database RAC model, services are represented as “/<service-name>.

String connString = "jdbc:oracle:thin:scott/tiger@//
mycomany.com:5521/sales"

Fast Event Notification, Connection Fail-over, and Load Balancing

The dynamic nature of an enterprise grid environment leads to a constantly changing system configuration in terms of node allocations/deallocations. To allow JDBC applications to meet these requirements a mechanism must be provided to rapidly propagate changes occurring at service, node, and database level. Upon receipt of the changes, the subscriber must take the appropriate action. The combination of Oracle Notification Services and Oracle JDBC Connection Cache Manager allows the following:

  • Monitoring of events

  • Rerouting of connection requests to surviving instances on failure events (Instance Down)

  • Repartitionning of the pool of connections across all active instances

JDBC Statements and Oracle Extensions

In the previous section, we looked at JDBC connections and Oracle extensions. Once you’ve got a connection object, you want to access and manage data through SQL statements. In this section, we look at the various JDBC statement types, statement caching, and statement batching.

JDBC Statement Types

As illustrated by Figure 7.2, the JDBC specification furnishes three types of statements that address different requirements:

  • Statement: For submitting basic and adhoc SQL statements to the RDBMS.

  • PreparedStatement: Derived from Statement, used for submitting (parameterized) SQL statements that are parsed once and executed many times; methods are furnished to replace input parameter placeholders with the actual values at runtime.

  • CallableStatement: Derived from PreparedStatement for calling stored procedures; methods are furnished for retrieving the values returned by output parameters.

JDBC Statement Types

Figure 7.2. JDBC Statement Types

Let’s look at each statement type in greater detail.

Statement

In a Nutshell

An instance of a statement object is obtained through the invocation of the createStatement() method on the Connection object.

Statement stmt = null;
ResultSet rs = null;
// assume conn refers to a connection object
stmt = conn.createStatement();

Once you get an instance of the Statement object, you invoke either the execute(), executeQuery(), or executeUpdate() methods:

  • executeQuery(String): For SQL queries, which return a ResultSet object (described later)

    try {
    
    // assume the statement is a SELECT...
    rs = stmt.executeQuery("SELECT ename FROM emp");
    } finally {...}
  • execute(String): For unknown, dynamic, adhoc SQL statements; it returns true if the SQL query was a SELECT or false if the SQL statement was a DML (i.e., UPDATE, INSERT, or DELETE). For DML, you retrieve the number of affected rows by invoking getUpdateCount() on the Statement instance; getUpdateCount returns –1 for queries (i.e. SELECT).

    if (stmt.execute("SELECT ename FROM emp")) {
            rs = stmt.getResultSet();
     }
    } finally {...}
  • executeUpdate: For DDL (i.e., create table) and DML statements (i.e., insert, update, delete), it returns the number of rows affected by the update statement.

    stmt.executeUpdate("update emp set ename = 'FOOBAR'
            where empno = '7934' ")

    JDBCPrimer.java, in section 6.1, is a basic use of Statement.

Standard Statement API and Oracle Extensions

The oracle.jdbc.OracleStatement interface captures both the standard Statement API as well as Oracle extensions.

The java.sql.Statement interface specifies methods supporting the execution of SQL statements, including addBatch, cancel, clearBatch, clearWarnings, close, execute, executeBatch, executeQuery, executeUpdate, getConnection, getFetchDirection, getFetchSize, getGeneratedKeys, getMaxFieldSize, getMaxRows, getMoreResults, getQueryTimeout, getResultSet, getResultSetConcurrency, getResultSetHoldability, getResultSetType, getUpdateCount, getWarnings, setCursorName, setEscapeProcessing, setFetchDirection, setFetchSize, setMaxFieldSize, setMaxRows, and setQueryTimeout.

  • execute, executeQuery, executeUpdate were addressed previously.

  • The addBatch, clearBatch, and executeBatch methods are discussed later.

  • Result Set–related methods (e.g., getFetchDirection, getFetchSize, getMaxFieldSize, getMaxRows, getMoreResults, getQueryTimeout, getResultSet, getResultSetConcurrency, getResultSetType, getUpdateCount, setCursorName, setFetchDirection, setFetchSize, setMaxFieldSize, setMaxRows) are discussed in the next chapter.

  • cancel sends a message to the RDBMS and waits for a reply. If, for whatever reason, the RDBMS is not responding, then the cancel will hang until the socket read times out.

  • setQueryTimeout sends Statement.cancel when it times out; it guarantees that it will not cancel the statement in less than the supplied number of seconds. A Statement timeout thread is started in each JVM to enforce timeouts.

    Note

    The cancel() and setQueryTimeout methods are not supported in Oracle’s server-side driver types.

  • The setEscapeProcessing(true|false) method, when invoked with “true,” instructs the driver to translate an RDBMS-independent format into Oracle RDBMS SQL format and data representations. The Oracle JDBC supports the following SQL 92 escape syntaxes:

    • Time and Date Literals: {d 'yyyy-mm-dd'} and {t 'hh:mm:ss'}

// Select ename from the emp where the hiredate is Nov-25-1995
       ResultSet rset = stmt.executeQuery
       ("SELECT ename FROM emp WHERE hiredate = {d '1995-11-25'}");
        or
       ("SELECT ename FROM emp WHERE hiredate = {t '12:00:00'}");
  • Timestamp Literals: {ts 'yyyy-mm-dd hh:mm:ss.f...'}

    ResultSet rset = stmt.executeQuery
    ("SELECT ename FROM emp WHERE hiredate = {ts '1995-11-
    25 12:00:00'}");
  • Scalar Functions: The Oracle JDBC drives support only the scalar function escape syntax: {fn <scalar function>};

  • LIKE escape characters

// Select empno from the emp where ename starts with '_'
 ResultSet rset = stmt.executeQuery
 ("SELECT empno FROM emp WHERE ename LIKE '&_%' {escape '&'}");

or:

("SELECT empno FROM emp WHERE ename LIKE '\_%' {escape ''}");
  • Outer Joins: The Oracle JDBC drivers do not support the standard outer join syntax.

    ResultSet rset = stmt.executeQuery
           ("SELECT ename, dname
                 FROM {OJ dept LEFT OUTER JOIN emp ON
    dept.deptno = emp.deptno}
                 ORDER BY ename");

    use Oracle’s own outer join syntax instead

    ResultSet rset = stmt.executeQuery
            ("SELECT ename, dname
    
                 FROM emp a, dept b WHERE a.deptno =
    
    b.deptno(+)
    
                ORDER BY ename");
  • Function Call Syntax: See section 7.4.4.

    The oracle.jdbc.OracleSql class furnishes the parse() method to translate the SQL92 syntax into an Oracle-supported syntax (See the SQL92 to SQL Syntax Example in the Oracle JDBC documentation).

    The oracle.jdbc.OracleStatement interface extends the standard java.sql.Statement with additional methods, including clearDefines(), closeWithKey(), creationState(), defineColumnType(), defineColumnTypeBytes(), defineColumnTypeChars(), getRowPrefetch(), isNCHAR(), setResultSetCache(), and setRowPrefetch(). These extensions mostly address performance optimizations.

  • defineColumnType() defines the data type to be used for retrieving values from a column. Without defineColumnType, JDBC might require additional roundtrip(s) to the database to get the definition from metadata in system tables. As of release 10.2, this method is no longer needed or recommended when using JDBC-Thin.

    Example:

    //
    ((OracleStatement)stmt).defineColumnType(1,Types.INTEGER);
    ((OracleStatement)stmt).defineColumnType(2,Types.VARCHAR,10);
    ResultSet rset = stmt.executeQuery();
     while (rset.next())
     System.out.println("INTEGER " + rset.getInt(1)+
       ", VARCHAR " + rset.getString(2));
    stmt.close();
    rset.close();
  • defineColumnTypeBytes(): Similar to defineColumnType but specifies the maximum size in bytes. The values for this column will not exceed the maximum.

  • defineColumnTypeChars(): Similar to defineColumnTypeBytes but instead specifies the maximum size in characters.

See the Oracle JDBC javadoc for more details on the various signatures.

For each SQL statement execution, a new cursor is created, then the statement is parsed and (if query) optimized, then executed; there is no bind phase because there is no parameter. However, the overhead incurred by a systematic parse might become expensive, so the JDBC specification defines PreparedStatement, our next topic.

PreparedStatement

In a Nutshell

PreparedStatement addresses the requirement of preparsing/preparing SQL statements that can be parameterized ahead of the execution phase. Most RDBMSs including Oracle, process SQL statements through four steps: parse, bind, execute, and, optionally, “fetch” for getting result sets. In the Statement object, there is no bind step; in the PreparedStatement case, the parse and bind steps are explicitly delineated. The bind step consists of replacing the placeholders of input parameters, which are represented by “?” in the SQL string, by the actual parameter values at runtime using either constant values or bind variables. Unlike Statement object, the cursor associated with the instance of the preparsed statement is not reinitialized/rebuilt after execution, which allows multiple executions of the same statement without reparsing.

Typical PreparedStatement SQL statements:

  • UPDATE emp SET deptno=? WHERE empno=?

  • INSERT INTO footab (id, code, descr) VALUES (?, ?, ?)

  • DELETE footab WHERE id = ?

  • SELECT col1, ?, job FROM tab WHERE col 2 = ? ORDER by ?

Note that the Oracle RDBMS does not support bind parameters as part of the IN clause of SELECT statements. In other words, the following syntax is not supported:

SELECT prod_id, prod_name from prod_tab where prod_id
in ( ?, ?, );

However, the following syntax, which uses a nested table as part of the IN clause works:

SELECT prod_id, prod_name
FROM prod_tab
WHERE prod_id IN (SELECT column_value FROM TABLE(?))

The following steps describe the typical use of PreparedStatement.

Steps 1 and 2

Initialize the PreparedStatement and set the value of the input parameters using

pstmt.setXXX(<column index>, <constant value>|<bind
variable>)

where:

<XXX> = standard Java types and Oracle Java types, addressed in the next chapter

<column index> = the index of the column (i.e., 1 for col 1, 2 for col 2, etc.)

<bind variable> = the name of a variable containing the input value

Example #1:

int empnum = 7934

PreparedStatement pstmt =
  conn.prepareStatement
("SELECT empno, ename FROM emp WHERE empno = ? ");
pstmt.setInt(1, empnum);
...

-------

Example #2:

int empnum = 7934

PreparedStatement pstmt =
  conn.prepareStatement("UPDATE emp
  SET ename = ? WHERE empno = ? ");
pstmt.setString(1, "FOOBAR");

pstmt.setInt(2, empnum);
...

Step 3

Execute the prepared statement using executeQuery() or executeUpdate() methods; however, in the PreparedStatement case, these methods have no parameters. The discussion on executeQuery() or executeUpdate() (See section 7.4.2) applies here too.

pstmt.executeUpdate();
...

Step 4

Dealing with Results, see Chapter 8 for more details on Result Set.

See Chapter 3for a basic but complete example of using PreparedStatement.

The Standard PreparedStatement API and OraclePreparedStatement

The oracle.jdbc.PreparedStatement interface captures both standard API as well as Oracle extensions.

The java.sql.PreparedStatement interface specifies standard methods for using prepared SQL statements, mostly for setting parameter values. These methods include addBatch, clearParameters, execute, executeQuery, executeUpdate, getMetaData, getParameterMetaData, setArray, setAsciiStream, setBigDecimal, setBinaryStream, setBlob, setBoolean, setByte, setBytes, setCharacterStream, setClob, setDate, setDouble, setFloat, setInt, setLong, setNull, setObject, setRef, setShort, setString, setTime, setTimestamp, setUnicodeStream, and setURL.

  • The clearParameters() method has been defined for clearing the values that have been set for the parameter placeholders; by default, these values are not reset through execution (unless new values are set).

  • The ParameterMetaData interface is new in JDBC 3.0 and provides information about the number, type, and properties of parameters to PreparedStatement (i.e., getParamaterMetaData()). It also specifies the ability to cache and reuse a PreparedStatement associated with a physical connection (see section 7.4.6).

    To set the values of input parameters (i.e., bind data for input), the Oracle JDBC driver uses three approaches depending on the data size (see Tables 7.4 and 7.5) and whether the input parameter is for SQL or PL/SQL operations. For example, LOB binding is applied for LOB parameter for PL/SQL when the data size is greater than 32,767:

    • direct binding: Where the data is placed in a bind buffer; it is fast and supports batching (statement batching is discussed later).

    • stream binding: Where the data is streamed; it is slower, because it may require roundtrips, and it does not support batching (it turns it off ).

    • LOB binding: Consists of creating a temporary LOB to hold the input data using the LOB APIs, and the bytes of the LOB locator are placed in the bind buffer; it is very slow because it requires many roundtrips to the RDBMS. Batching is supported but not recommended.

    Table 7.4. Size Limits and Bind Mechanisms

    Form

    Stmt

    Driver Type

    Lower Limit

    Upper Limit

    Bind Mechanism

    All

    All

    All

    0

    0

    Null

    All

    SQL

    Client

    1 char

    32,766 chars

    Direct

          

    All

    SQL

    Client

    32,767 chars

    2,147,483,647 bytes

    Stream

    All

    SQL

    Client

    2,147,483,648 bytes

    2,147,483,647 chars

    Temp Clob

    CHAR

     

    Server

    1 char

    65,536 bytes

    Direct

    NCHAR

      

    1 char

    4,000 bytes

    Direct

    NCHAR

      

    4,001 bytes

    2,147,483,647 chars

    Temp Clob

    CHAR

      

    65,537 bytes

    2,147,483,647 bytes

    Stream

       

    2,147,483,647 bytes

    2,147,483,647 chars

    Temp Clob

          

    All

    PL/SQL

    All

    1 char

    32,512 chars

    Direct

    All

    PL/SQL

    All

    32,513 chars

    2,147,483,647 chars

    Temp Clob

    Table 7.5. Size Limits and Bind Mechanisms

    Stmt

    Driver Type

    Lower Limit

    Upper Limit

    Bind Mechanism

    SQL

    Client

    32,767 bytes

    2,147,483,648 bytes

    Stream

         

    All

    All

    0

    0

    Null

    SQL

    All

    1

    2,000 bytes

    Direct

    SQL

    All

    2,000 bytes

    2,147,483,647 bytes

    Stream

         

    PL/SQL

    All

    1

    32,512 bytes

    Direct

    PL/SQL

    All

    32,513 bytes

    2,147,483,647 bytes

    Temp blob

    Prior to 10g Release 2, the Oracle JDBC implementation of setString, setCharacterStream, setAsciiStream, setBytes, and setBinaryStream methods for setting the value(s) of input parameter(s) worked as follows:

    setBytes: performs a direct bind of bytes

    setBinaryStream: performs a stream bind of bytes

    In 10g Release 2, the implementation has changed as follows:

    • The switching between binding modes is fully automatic, contrary to 10g Release 1, where a SQL exception is thrown if the parameter size is larger than the threshold of the bind method, listed in Tables 7.4 and 7.5, and based on the type of the SQL statement:

    • setBytes switches to setBinaryStream (i.e., stream bind of bytes) for SQL operations (i.e., PreparedStatement) on data larger than 2,000 bytes.

    • setBytes switches to setBinaryStream for PL/SQL operations (i.e., CallableStatement) on data larger than 2,000 bytes and to setBytesForBlob for data larger than 32,512 bytes.

    • setString switches to setCharacterStream (i.e., stream bind of characters) for SQL operations on data larger than 32,766 characters.

    • setString switches to setStringForClob for string data larger than 32,512 bytes in the database character set or national character set, depending on whether setFormOfUse has been used for NCLOB parameters.

    For fixed-length character sets, data byte length is equal to the length of the Java character data multiplied by the character size in bytes.

    For variable-length character sets, data byte length is computed as follows:

    • Direct bind is used if the length of the character set is less than 32,512 divided by the maximum character size.

    • LOB bind is used if the length of the character set is greater than 32,512 divided by the minimum character size.

    • Direct bind is used if the length of the character set is between and if the actual length of the converted bytes is less than 32,512; otherwise, LOB bind is used.

    Table 7.4 describes the size limits and the bind approach used by setString, setCharacterStream and setAsciiStream, according to the size of data.

    Table 7.5 describes the size limits and the bind approach used by setBytes and setBinaryStream, according to the size of data.

    Methods inherited from the java.sql.Statement interface: addBatch, cancel, clearBatch, clearWarnings, close, execute, executeBatch, executeQuery, executeUpdate, getConnection, getFetchDirection, getFetchSize, getGeneratedKeys, getMaxFieldSize, getMaxRows, getMoreResults, getMoreResults, getQueryTimeout, getResultSet, getResultSetConcurrency, getResultSetHoldability, getResultSetType, getUpdateCount, getWarnings, setCursorName, setEscapeProcessing, setFetchDirection, setFetchSize, setMaxFieldSize, setMaxRows, setQueryTimeout.

    Methods inherited from the oracle.jdbc.OracleStatement interface: clearDefines, closeWithKey, creationState, defineColumnType, defineColumnTypeBytes, defineColumnTypeChars, getRowPrefetch, isNCHAR, setResultSetCache, setRowPrefetch

    The Oracle extensions consist of the following methods: defineParameterType, defineParameterTypeBytes, defineParameterTypeChars, getExecuteBatch, getReturnResultSet, OracleGetParameterMetaData, registerReturnParameter, setARRAY, setArrayAtName, setARRAYAtName, setAsciiStreamAtName, setBfile, setBFILE, setBfileAtName, setBFILEAtName, setBigDecimalAtName, setBinaryDouble, setBinaryDoubleAtName, setBinaryFloat, setBinaryFloatAtName, setBinaryStreamAtName, setBLOB, setBlobAtName, setBLOBAtName, setBooleanAtName, setByteAtName, setBytesForBlob, setBytesForBlobAtName, setCHAR, setCHARAtName, setCheckBindTypes, setCLOB, setClobAtName, setCLOBAtName, setCursor, setCursorAtName, setCustomDatum, setCustomDatumAtName, setDATE, setDateAtName, setDATEAtName, setDisableStmtCaching, setDoubleAtName, setFixedCHAR, setFixedCHARAtName, setFloatAtName, setFormOfUse, setIntAtName, setINTERVALDS, setINTERVALDSAtName, setINTERVALYM, setINTERVALYMAtName, setLongAtName, setNullAtName, setNUMBER, setNUMBERAtName, setObjectAtName, setOPAQUE, setOPAQUEAtName, setOracleObject, setOracleObjectAtName, setORAData, setORADataAtName, setPlsqlIndexTable, setRAW, setRAWAtName, setREF, setRefAtName, setREFAtName, setRefType, setRefTypeAtName, setROWID, setROWIDAtName, setShortAtName, setStringAtName, setStringForClob, setStringForClobAtName, setSTRUCT, setSTRUCTAtName, setStructDescriptor, setStructDescriptorAtName, setTimeAtName, setTIMESTAMP, setTimestampAtName, setTIMESTAMPAtName, setTIMESTAMPLTZ, setTIMESTAMPLTZAtName, setTIMESTAMPTZ, setTIMESTAMPTZAtName, setUnicodeStreamAtName, setURLAtName.

    Most of these methods deal with binding java.sql.* and oracle.sql.* types to either parameter indexes (i.e., setXXX()) or parameter names (i.e., setXXXAtName()) and statement batching (i.e., getExecuteBatch()).

    registerReturnParameter(): See DML with Returning section, below.

    defineParameterType (<col index>, <Type constant>, <max-size> );

    where:

    <Type constant> = java.sql.Types or oracle.sql.OracleTypes
    <max-size> = the maximum size of data to bind in

    Example:

    SQL> CREATE TABLE pstmttab ( iname NUMBER, cname
    VARCHAR2(1096));
    
    OraclePreparedStatement pstmt =
    (OraclePreparedStatement) conn.prepareStatement("INSERT INTO"
           "+ pstmsttab(iname, cname) VALUES (?, ?)");
    
    pstmt.defineParameterType(2, OracleTypes.VARCHAR, 1096);

    setXXX(int, XXX): Standard and Oracle-specific methods for binding java.sql.* and oracle.sql.* types to RDBMS SQL types. In Chapter 3, Table 3.2 and 3.3 summarize the mapping of SQL and PL/SQL types to Java/JDBC types. Table 7.6 summarizes the setXXX() method to use per Java input parameter type.

    Table 7.6. setXXX Method versus Input Parameter Types

    Input Parameter Type

    setXXX method(s)

    int

    setInt()

    short

    setShort()

    float

    setFloat()

    double

    setDouble()

    long

    setLong()

    byte

    setByte()

    byte[]

    setBytes()

    oracle.sql.NUMBER

    setNUMBER()

    java.sql.Array

    setArray()

    oracle.sql.ARRAY

    setARRAY()

    java.io.InputStream

    setAsciiStream(), setBinaryStream(), setUnicodeStream()

    java.io.Reader

    sSetCharacterStream()

    oracle.sql.BFILE

    setBFILE(), setBfile()

    BigDecimal

    setBigDecimal()

      

    oracle.sql.BINARY_FLOAT

    setBinaryFloat()

    oracle.sql.BINARY_DOUBLE

    setBinaryDouble()

    java.sql.Blob

    setBlob()

    oacle.sql.BLOB

    setBLOB()

    java.sql.Clob

    setClob()

    oracle.sql.CLOB

    setCLOB()

    Boolean

    seBoolean()

    java.lang.String

    setString(), setFixedCHAR()

    oracle.sql.CHAR

    setCHAR(), setFixedCHAR()

    java.sql.Date

    setDate()

    oracle.sql.DATE

    setDATE()

    java.sql.Time

    setTime()

    java.sql.Timestamp

    setTimestamp()

    oracle.sql.TIMESTAMP

    setTIMESTAMP()

    oracle.sql.TIMESTAMPTZ

    setTIMESTAMPTZ()

    oracle.sql.TIMESTAMPLTZ

    setTIMESTAMPLTZ()

    oracle.sql.RAW

    setRAW()

    java.sql.Ref

    setRef()

    oracle.sql.REF

    setREF()

    oracle.sql.ROWID

    setROWID()

    oracle.sql.STRUCT

    setSTRUCT()

    java.net.URL

    setURL()

    setNull(parameterIndex, sqlTYpe), setNull(parameterIndex,
    sqlTYpe, sql_type_name) : For setting an object to NULL.

    setCHAR(): Because CHAR SQL type is right-padded with spaces to the column width, setCHAR performs a padded comparison when used to bind character data in a WHERE clause; use setFixedCHAR() instead; it performs a nonpadded comparison.

    Assume a table, pstmttab, with columns col 1 to col 18 corresponding to the various oracle.sql.* data types. The following code snippet illustrates the invocation of the various setXXX methods:

    String stmt = "insert into pstmttab values (?, ?, ?, ?, ?." +
    "?,?,?,?,?,?,?,?,?,?)";;
    
    OraclePreparedStatement opstmt =
            (OraclePreparedStatement)con.prepareStatement(stmt);
    
    opstmt.setInt(1, index);
    opstmt.setString(2, "This is a String");
    opstmt.setShort(3, (short)12);
    
    opstmt.setLong(4, (long)12345);
    opstmt.setFloat(5, (float)123.456);
    opstmt.setDouble(6, (double)12345.678);
    opstmt.setBigDecimal(7, new BigDecimal(12.34));
    opstmt.setBoolean(8, true);
    opstmt.setDATE(9, date);
    opstmt.setTIMESTAMP(10, xts);
    opstmt.setTIMESTAMPTZ(11, xtstz);
    opstmt.setTIMESTAMPLTZ(12, xtsltz);
    opstmt.setCLOB(13, xclb);
    opstmt.setBLOB(14, xblb);
    opstmt.setAsciiStream(15, is, xlength);
    opstmt.setRAW(16, new RAW((Object)rawString));
    opstmt.setBFILE(17, xbfile);
    opstmt.setObject(18, xobject);

    setOracleObject: A generic set XXX method for binding oracle.sql.* data as an oracle.sql.Datum object. The oracle.sql.Datum is the root class of Oracle’s native data type classes, including BINARY_DOUBLE, BINARY_FLOAT, CHAR, DATE, ARRAY, NUMBER, REF, INTERVALDS, INTERVALYM, NUMBER, RAW, ROWID, TIMESTAMP, TIMESTAMPLTZ, TIMESTAMPTZ. It may be used to bind any data type.

    setFormOfUse(): Because the NCHAR SQL data type is similar to the SQL CHAR data type (i.e., CHAR, VARCHAR2, and CLOB) and has no separate corresponding classes defined in the oracle.sql.* package, JDBC programs that manipulate the NCHAR SQL data type must call the setFormOfUse() method to specify that the data will be bound to the SQL NCHAR data type.

    setFormOfUse(int parameterIndex, short formOfUse)

    formOfUse: Sets the form to use and takes two values: FORM_CHAR and FORM_NCHAR.

    • FORM_CHAR for CHAR, VARCHAR2, and CLOB data; it is the default value.

    • FORM_NCHAR for NCHAR, NVARCHAR2, and NCLOB data; JDBC will represent the data in the national character set of the server internally.

Example:

int empno = 12345;
String ename = "uFF2AuFF4FuFF45";
String job = "Manager";
oracle.jdbc.OraclePreparedStatement pstmt =
(oracle.jdbc.OraclePreparedStatement)
conn.prepareStatement
    ("INSERT INTO emp (empno, ename, job) VALUES(?, ?, ?)");
pstmt.setFormOfUse(2, FORM_NCHAR);
pstmt.setFormOfUse(3, FORM_NCHAR);
pstmt.setInt(1, 1);
pstmt.setString(2, ename);
pstmt.setString(3, job);
pstmt.execute();

setXXXAtName(String, XXX): An Oracle extension similar to setXXX(), which allows binding by name, as illustrated below.

opstmt.setIntAtName ("col1", 12);
opstmt.setStringAtName("col2", "string");
opstmt.setFloatAtName("col3", (float)1.2);
opstmt.setBooleanAtName("col4", true);
opstmt.setShortAtName("col5", (short)7);
opstmt.setBigDecimalAtName("col7", new BigDecimal(123));
opstmt.setDoubleAtName("col8", 1.23);
opstmt.setIntAtName("col9", 12);
opstmt.setLongAtName("col10", (long)123);

Working with Oracle data types is covered in Chapter 8.

Performance

The PreparedStatement implementation is much more efficient and faster in Oracle Database 10g than in previous releases; see the eBay testimonial[11] on the average performance gain when moving to 10g JDBC. However, the very first PreparedStatement object since the creation of the Java VM instance pays a one-time and barely noticeable price to load a few helper classes (one class loader per JVM).

CallableStatement (Calling Stored Procedures)

CallableStatements are designed for invoking stored procedures in JDBC. The Oracle JDBC drivers support Java and PL/SQL stored procedures. There is an abundant PL/SQL literature. Java in the database is traditionally invoked through a PL/SQL wrapper, using CallableStatement, which is the topic of this section; however, as described previously, Java in the database can also be invoked using a client-side stub. Defining PL/SQL wrappers for Java in the database was covered earlier.

In a Nutshell

Processing a CallableStatement involves the following five steps:

Step 1

Create and initialize a CallableStatement object using the prepareCall(String) method of the Connection object. The parameters are specified using the same placeholder notation as in PreparedStatement. The prepareCall method in Oracle JDBC drivers supports both the SQL92 and the anonymous PL/SQL block syntaxes.

  • In the SQL 92 syntax, the string has two forms. The first form invokes a stored function with a result parameter, while the second form invokes a stored procedure:

    // {? = call func (...) } -- A result is returned to a
    // variable
    CallableStatement cstmt1 =
        conn.prepareCall("{? = call func1(?, ?)}");
    ...
    
     // {call proc (...) }   -- Does not return a result
     CallableStatement cstmt2 =
         Conn.prepareCall("{call proc2 (?, ?)}");
  • Similarly, the anonymous PL/SQL block syntax also has two forms:

    // begin ? := func (?, ?); end; -- a result is
    // returned to a variable
    CallableStatement cstmt3 =
        conn.prepareCall("begin ? := func3(?, ?); end;");
    ...
    
    // begin proc(?, ?); end;  -- Does not return a result
    CallableStatement cstmt4 =
        Conn.prepareCall("begin proc4(?, ?); end;");

Step 2

In order to retrieve the values from the CallableStatement object when the stored procedures successfully return, you must register the data type of the OUT and IN/OUT parameters using the registerOutParameter (index, parameter type) method of CallableStatement. The OraTypes (oracle.jdbc.OracleTypes) entry in the javadoc of the Oracle JDBC defines the valid SQL types that can be used as parameter types, including ARRAY, BFILE, BIGINT, BINARY, BINARY DOUBLE, BINARY FLOAT, BIT, BLOB, BOOLEAN, CHAR, CLOB, CURSOR, DATE, DOUBLE, FIXED_CHAR, INTEGER, INTERVALDS, INTERVALYM, JAVA_OBJECT, JAVA_STRUCT, LONGVARBINARY, LONGVARCHAR, NULL, NUMBER, OPAQUE, REF, RAW, ROWID, STRUCT, TIMESTAMP, TIMESTAMPLTZ, TIMESTAMPTZ, VARCHAR, and so on.

The following instruction assumes a VARCHAR is returned:

cstmt1.registerOutParameter(1,Types.VARCHAR);
...

Step 3

Similar to PreparedStatement, you must set the value(s) for input parameters:

cstmt1.setString(2, "aadfadfad");
cstmt1.setInt(3, 125);
...

Step 4

Execute the CallableStatement. While CallableStatement supports calling any of the Statement execute methods (i.e., executeUpdate(), executeQuery(), or execute()), execute() is the most flexible method because it does not require you to know ahead of time if the stored procedure returns result sets or not.

cstmt1.execute();
...

Step 5

Get the Output value(s) using the getXXX() method corresponding to the previously registered type (in step 2). See more details on getXXX() in the following text.

// var = cstmt.getXXX(index);
String outParam = cstmt1.getString(1);

Examples:

// Calling a procedure which has no parameter
try (
  cstmt = conn.prepareCall("{call p0}");
  cstmt.execute();
} catch (SQLException ex) {
}

// Calling a function which has no parameter but returns
// a VARCHAR
try (
cstmt2 = conn.prepareCall("{? = call f0o}");
cstmt.registerOutParameter(1, Types.VARCHAR);
cstmt.execute();
String res = cstmt.getString(1);
} catch (SQLException ex) {
}


// Calling a procedure which has one IN parameter
try (
cstmt = conn.prepareCall("{call p1i(?)}");
cstmt.setString(1, "foo");
cstmt.execute();
} catch (SQLException ex) {
}

/*
 * Calling a stored procedure with no parameters
 *
 */

 {
    CallableStatement proc0 = conn.prepareCall
    ("begin foo; end;");
    proc0.execute ();
 }

/*
 * Calling a procedure with an IN parameter
 *
 */
 {
  CallableStatement p1in =
              conn.prepareCall ("begin proc1 (?);
end;");
  p1in.setString (1, "foobar");
  p1in.execute ();
}
†

/*
 * Calling a procedure with an OUT parameter
 *
 */
 {
    CallableStatement p1out =
           conn.prepareCall ("begin ocstmt (?); end;");
    p1out.registerOutParameter (1, Types.CHAR);
    p1out.execute ();
    System.out.println ("Out argument is: "+
p1out.getString (1));
 }

 /*
  * Calling a procedure with an IN/OUT prameter
  *
  */
  {
    CallableStatement p1inout =
             conn.prepareCall ("begin procinout (?);
end;");
    p1inout.registerOutParameter (1, Types.VARCHAR);
    p1inout.setString (1, "foobar");

    p1inout.execute ();
    System.out.println ("Out argument is: " +
p1inout.getString (1));
  }

/*
 * Calling a function with no parameters
 *
 */
 {
  CallableStatement f0 =
              conn.prepareCall ("begin ? := funcnone;
end;");
   f0.registerOutParameter (1, Types.CHAR);
   f0.execute ();
      System.out.println ("Return value is: "+
f0.getString (1));
  }

/*
 * Calling a function with an IN parameter
 *
 */
 {
   CallableStatement f1in =
             conn.prepareCall ("begin ? := funcin (?);
end;");
   f1in.registerOutParameter (1, Types.CHAR);
   f1in.setString (2, "aaaaaaaaaaa");
   f1in.execute ();
   System.out.println ("Return value is: " +
f1in.getString (1));
 }


/*
 * Calling a function with an OUT parameter
 *
 */
 {
     CallableStatement f1out =
             conn.prepareCall ("begin ? := funcout (?);

end;");
      f1out.registerOutParameter (1, Types.CHAR);
      f1out.registerOutParameter (2, Types.CHAR);
      f1out.execute ();
      System.out.println ("Return value is: " +
f1out.getString (1));
      System.out.println ("Out argument is: " +
f1out.getString (2));
    }

The Standard API and OracleCallableStatement

The java.sql.CallableStatement interface extends java.sql.PreparedStatement (see the corresponding javadoc for the list of inherited methods) and specifies the following methods: getArray, getBlob, getBoolean, getByte, getBytes, getClob, getDate, getDouble, getFloat, getInt, getLong, getObject, getRef, getShort, getString, getTime, getTimestamp, getURL, registerOutParameter, setAsciiStream, setBigDecimal, setBinaryStream, setBoolean, setByte, setCharacterStream, setDate, setDouble, setFloat, setInt, setLong, setNull, setObject, setShort, setString, setTime, setTimestamp, setURL, wasNull.

  • The JDBC 3.0 API specifies multiple open ResultSet as the ability to have multiple ResultSet open at the same time, from a CallableStatement. A new signature to getMoreResults(), which now takes a flag (i.e., getMoreResults(int)), allows controlling how to handle previously opened ResultSet upon the invocation of getResultSet().

  • The JDBC 3.0 API also specifies Named Parameters in CallableStatements as the ability to reference parameters to CallableStatement objects by name as well as by index (e.g., getXXX(column-name)).

Note

Named Parameter is not supported for RefCursors/ResultSets.

Methods inherited from oracle.jdbc.OraclePreparedStatement:

The oracle.jdbc.Callable interface extends the oracle.jdbc.OraclePreparedStatement interface and inherits the following methods (addressed in section 7.4.3):

defineParameterType, defineParameterTypeBytes,
defineParameterTypeChars, getExecuteBatch,
getReturnResultSet, OracleGetParameterMetaData,
registerReturnParameter, setARRAY, setArrayAtName,
setARRAYAtName, setAsciiStreamAtName, setBfile, setBFILE,
setBfileAtName, setBFILEAtName, setBigDecimalAtName,
setBinaryDouble, setBinaryDoubleAtName, setBinaryFloat,
setBinaryFloatAtName, setBinaryStreamAtName, setBLOB,
setBlobAtName, setBLOBAtName, setBooleanAtName,
setByteAtName, setBytesForBlob, setBytesForBlobAtName,
setCHAR, setCHARAtName, setCheckBindTypes, setCLOB,
setClobAtName, setCLOBAtName, setCursor, setCursorAtName,
setCustomDatum, setCustomDatumAtName, setDATE, setDateAtName,
setDATEAtName, setDisableStmtCaching, setDoubleAtName,
setFixedCHAR, setFixedCHARAtName, setFloatAtName,
setFormOfUse, setIntAtName, setINTERVALDS,
setINTERVALDSAtName, setINTERVALYM, setINTERVALYMAtName,
setLongAtName, setNullAtName, setNUMBER, setNUMBERAtName,
setObjectAtName, setOPAQUE, setOPAQUEAtName,
setOracleObject, setOracleObjectAtName, setORAData,
setORADataAtName, setPlsqlIndexTable, setRAW,
setRAWAtName, setREF, setRefAtName, setREFAtName,
setRefType, setRefTypeAtName, setROWID, setROWIDAtName,
setShortAtName, setStringAtName, setStringForClob,
setStringForClobAtName, setSTRUCT, setSTRUCTAtName,
setStructDescriptor, setStructDescriptorAtName,
setTimeAtName, setTIMESTAMP, setTimestampAtName,
setTIMESTAMPAtName, setTIMESTAMPLTZ,
setTIMESTAMPLTZAtName, setTIMESTAMPTZ,
setTIMESTAMPTZAtName, setUnicodeStreamAtName,
setURLAtName.

OracleCallableStatement Extensions to java.sql.CallableStatement

getAnyDataEmbeddedObject, getARRAY, getAsciiStream, getBFILE,
getBinaryStream, getBLOB, getCHAR, getCharacterStream,
getCLOB, getCursor, getDATE,  getINTERVALDS, getINTERVALYM,
getNUMBER, getOPAQUE, getOracleObject,
getOraclePlsqlIndexTable, getORAData, getPlsqlIndexTable,
getRAW, getREF, getROWID, getSTRUCT, getTIMESTAMP,
getTIMESTAMPLTZ, getTIMESTAMPTZ, getUnicodeStream,
registerIndexTableOutParameter, registerOutParameter,
sendBatch, setBinaryDouble, setBinaryFloat, setBytesForBlob,
setExecuteBatch, setStringForClob.

getXXX(): Similar to setXXX(), these methods return Java data types, as illustrated in Table 7.7.

Table 7.7. getXXX() and Returned Object

getXXX()

Returned Object Type

getInt()

int

getShort()

short

getFloat()

float

getDouble()

double

getLong()

long

getByte()

byte

getBytes()

byte[]

getNUMBER()

oracle.sql.NUMBER

getArray()

java.sql.Array

getARRAY()

oracle.sql.ARRAY

getAsciiStream(), getBinaryStream(), getUnicodeStream()

java.io.InputStream

getCharacterStream()

java.io.Reader

getBFILE(), getBfile()

oracle.sql.BFILE

getBigDecimal()

BigDecimal

  

getBinaryFloat()

oracle.sql.BINARY_FLOAT

getBinaryDOuble()

oracle.sql.BINARY_DOUBLE

getBlob()

java.sql.Blob

getBLOB()

oacle.sql.BLOB

getClob()

java.sql.Clob

getCLOB()

oracle.sql.CLOB

getBoolean()

Boolean

getString(), getFixedCHAR()

java.lang.String

getCHAR(), getFixedCHAR()

oracle.sql.CHAR

getDate()

java.sql.Date

getDATE()

oracle.sql.DATE

getTime()

java.sql.Time

getTimestamp()

java.sql.Timestamp

getTIMESTAMP()

oracle.sql.TIMESTAMP

getTIMESTAMPTZ()

oracle.sql.TIMESTAMPTZ

getTIMESTAMPLTZ()

oracle.sql.TIMESTAMPLTZ

getRAW()

oracle.sql.RAW

getRef()

java.sql.Ref

getREF()

oracle.sql.REF

getROWID()

oracle.sql.ROWID

getSTRUCT()

oracle.sql.STRUCT

getURL()

java.net.URL

getObject: Generic method for retrieving any java.sql.* data into an java.lang.Object object.

getOracleObject: Generic method for retrieving any oracle.sql.* data into an oracle.sql.Datum object.

Examples:

/* Example#1
 * Basic CallableStatement using Cursor
 *
 */

OracleCallableStatement cstmt =
  (OracleCallableStatement) conn.prepareCall
       ("begin select cursor(select 1 from dual) into ? from dual;
end;");

cstmt.registerOutParameter(1, OracleTypes.CURSOR);

ResultSet rs = cstmt.executeQuery();

// returns true if the column is of type NCHAR/NVARCHAR/NCLOB
cstmt.isNCHAR(1);

while(rs.next())
System.out.println ("column1 = " + rs.getString(1));
cstmt.close();


/* Example#2
 * CallalableStatement with TIMESTAMP dtatypes OUT parameters
 * Assume a procedure timeproc (?, ?, ?, ?)
 */


String dateStr = "2005-09-03 12:31:56.78";
Timestamp tss = Timestamp.valueOf(dateStr);
TimeZone tz = TimeZone.getDefault();
tz.setID("US/Pacific");
greg_cal = new GregorianCalendar(tz);

tsltz = new oracle.sql.TIMESTAMPLTZ(conn, tss, gcal);
tstz = new oracle.sql.TIMESTAMPTZ(conn, tss, gcal);
ts = new oracle.sql.TIMESTAMP(tss);
date = new oracle.sql.DATE(tss);
long longTime = tss.getTime();
time = new Time(longTime);
dateTime = new java.sql.Date(longTime);

CallableStatement ocstmt = null;

((oracle.jdbc.OracleConnection) conn).setSessionTimeZone("GMT");

ocstmt = conn.prepareCall ("begin timeproc (?, ?, ?, ?); end;");

((OracleCallableStatement)ocstmt).setTIMESTAMPLTZ(1, tsltz);
((OracleCallableStatement)ocstmt).registerOutParameter
                                  (1, OracleTypes.TIMESTAMPLTZ);

((OracleCallableStatement)ocstmt).setTIMESTAMPTZ(2, tstz);
((OracleCallableStatement)ocstmt).registerOutParameter
                                   (2, OracleTypes.TIMESTAMPTZ);

((OracleCallableStatement) ocstmt).setTIMESTAMP(3, ts);
((OracleCallableStatement) ocstmt).registerOutParameter
                                       (3, Types.TIMESTAMP);

((OracleCallableStatement) ocstmt).setTimestamp(4, tss, greg_cal);
((OracleCallableStatement) ocstmt).registerOutParameter
                                  (<index>, Types.TIMESTAMP);
ocstmt.executeUpdate();
ocstmt.close();

Performance

Connection.prepareCall() requires metadata retrieval for processing output parameters and is therefore an expensive method; reusing instances of CallableStatement reduces such cost (see section 7.4.6). See more on performance and best practices in Chapter 9.

Retrieval of Auto-Generated Keys and DML with Returning

JDBC 3.0 Auto-Generated Keys

In release 10.2, the Oracle JDBC drivers support the JDBC 3.0 Retrieval of Auto-Generated Key. To illustrate this feature, let’s take a J2EE BMP entity bean instance, which is uniquely identified by the primary key associated with the newly inserted data as the result of ejbCreate(). Inserting the data and retrieving the value of the primary key within ejbCreate() usually involves two database operations: (1) an INSERT statement, and then (2) the retrieval of the key of the newly inserted row, which in the Oracle RDBMS case could be the ROWID or a sequence number. Using the standard JDBC 3.0 Retrieval of the Auto-Generated Key feature, these two database operations are combined into one, resulting in performance optimization. The following interfaces have been extended to support auto-generated keys:

java.sql.DatabaseMetaData:

  • Public Boolean supports GeneratedKeys();

java.sql.Statement:

  • 
    Public Boolean execute(String sql, int autoGeneratedKeys)
          throws SQLException;
  • Public Boolean execute(String sql, int[] columnIndexes)
          throws SQLException;
  • Public

    Boolean execute(String sql, int[] columnNames)
           throws SQLException;
  • Public Boolean executeUpdate(String sql, int autoGeneratedKeys)
           throws SQLException;
  • Public Boolean executeUpdate(String sql, int[] columnIn
    dexes)
          throws SQLException;
  • Public Boolean executeUpdate(String sql, int[] column-
    Names)
         throws SQLException;

    java.sql.Connection: Methods that extend PreparedStatement objects:

  • Public PreparedStatement preparedStatement
           (String sql, int autoGeneratedKeys) throws
           SQLException;
  • Public PreparedStatement preparedStatement
          (String sql, int[] columnIndexes) throws SQLException;
  • Public PreparedStatement preparedStatement
          (String sql, String[] columnNames) throws SQLException;
  • Public ResultSet getGeneratedKeys() throws SQLException;

Specifying Statement.RETURN_GENERATED_KEYS tells executeUpdate to return the generated key(s):

ResultSet rset = null;
Statement stmt = conn.createStatement();

int countrows stmt.executeUpdate("INSERT INTO prodtab " +
                   "(prodid, prodname) " +
                   "VALUES (12345,'abdcefghi')",
                   Statement.RETURN_GENERATED_KEYS);

// Obtain the result set object from the query.
rs = stmt.getGeneratedKeys();

// Retrieve the auto generated key.
while (rset.next())
     int key = rs.getInt(1);
rset.close();

Use the DatabaseMetaData.supportsGetGeneratedKeys method to check that a JDBC driver and underlying RDBMS support the retrieval of auto-generated keys.

The Oracle JDBC implementation of the retrieval of auto-generated keys is based on the JDBC support for “DML with the RETURNING clause”; it works only for the INSERT statement and is not supported in server-side drivers.

DML with RETURNING

In Release 10.2, the Oracle JDBC drivers support DML with RETURNING, which is an Oracle RDBMS feature that lets you combine a query (in the RETURNING clause) with a DML into a single statement. It is more general purpose than the JDBC 3.0 Auto-Generated Keys, because it allows you to return any columns. However, it is not currently supported by server-side drivers. The following Oracle JDBC methods support DML with RETURNING:

registerReturnParameter(): Registers the return parameter for DML with Returning; it can only be used with CHAR or RAW types.

  • registerreturnparameter(int paramIndex, int externalType)

    throws SQLException;
  • registerreturnparameter(int paramIndex, int externalType,

  • int maxSize) throws SQLException;

  • registerReturnParameter(int paramIndex, int intsqlType,

    String typeName) throws SQLException;

    getReturnResulSet(): Fetches the data returned from DML with Returning as a ResultSet object.

    // Same example as above, using DML with RETURNING
    
    OracleResultSet orset = null;
    OraclePreparedStatement opstmt = null;
    
    opstmt = (OraclePreparedStatement) conn.prepareStatement(
      "insert into prodtab (prodid, prodname)
    values(12345,'abdcefghi') returning "
                    + "prodid, prodname into ?, ?");
    opstmt.registerReturnParameter(1, OracleTypes.NUMBER);
    opstmt.registerReturnParameter(2, OracleTypes.CHAR);
    
    int countrows = opstmt.executeUpdate();
    
    orset = (OracleResultSet) opstmt.getReturnResultSet();
    
    while (orset.next())
     System.out.println("Product Id: " + orset.getInt(1)+
       ", Product Name:  " + orset.getString(2));
    
    orset.close();

DML with RETURNING has the following limitations:

  • It cannot be used with batch update.

  • It does not support streams.

  • It is used under the covers for implementing auto-generated keys; therefore, these two features cannot be used in the same statement.

  • The returned ResultSet objects do not support ResultSetMetaData.

Statement Caching

This section explains the concept of statement caching, the various flavors of statement caching supported by the Oracle JDBC drivers, and how to enable and disable these features.

In a Nutshell

JDBC 3.0 introduces statement pooling or statement caching. Statement caching consists of caching the cursors associated with statement objects. Statement caching improves performance by caching executable statements for search and reuse if there is a match.

Statement caching does the following:

  • Eliminates the overhead of repeated cursor creation

  • Prevents repeated statement parsing and creation

  • Reuses metadata and/or data structures on the client side

A statement cache is associated with a physical connection (pooled or not); consequently, and contrary to what Java developers would expect, statement caching cannot cross the boundary of the physical connection. In other words, statement objects cannot be shared across physical connections; logical connections will share the same statement cache only if they share the same physical connection.

The Oracle JDBC drivers support both implicit and explicit statement caching schemes; both can be simultaneously enabled against the same cache.

Implicit Statement Caching

Implicit statement caching is Oracle’s implementation of JDBC 3.0 statement pooling; it is managed by the driver and hence transparent to the application code (at least caching operations). The Oracle JDBC driver automatically caches the PreparedStatement or the CallableStatement objects upon the invocation of the close() method. As stated in section 7.4.2, a new cursor is created for each execution of a Statement object (i.e., the cursor is not maintained upon Statement.close()). On the other hand, for PreparedStatements (OraclePreparedStatement) and CallableStatement (OracleCallableStatement) objects, the cursors are kept after the closing of these objects and not recreated for each new execution of the prepared or callable statements.

Implicit statement caching is enabled using one of the two following approaches:

  1. Invoke ((OracleConnection)conn).setImplicitCachingEnabled(true); on the connection object.

  2. Set the ImplicitCachingEnabled property to true and invoke OracleDataSource.getConnection().

    • OracleDataSource.setImplicitCachingEnabled(true);

    • OracleDataSource.getConnection();

To check whether implicit statement caching has been enabled or not, use the following Boolean method: getImplicitCachingEnabled();

The cache size—for both implicit and explicit statement caching—can be set:

  • Either at the data source level (for all pooled connections):

    // Set the statement cache size at the pooled connection
    // level
    ((OraclePooledConnection)pc).setStmtCacheSize(10);
  • Or at each pooled connection level:

// Set & Get the statement cache size at the data source
// level
OracleConnectionCacheImpl ods =
        new OracleConnectionCacheImpl(ocpds);
 ods.setStmtCacheSize(12);

A cache entry is automatically allocated to a new SQL statement—if not already in the cache—upon the invocation of preparedStatement() and prepareCall() methods. The Oracle JDBC driver transparently searches the cache for a match using the following criteria:

  • The SQL string must be identical.

  • The statement type must be identical PreparedStatement or CallableStatement.

  • The type of scrollable result sets FORWARD-ONLY or SCROLLABLE must be the same.

If a match is found, then the cached statement is returned; otherwise, a new statement is created and returned. When a cached statement is retrieved, its cursor and state are automatically reinitialized to their default values, while the metadata is preserved.

In either case, the statement, its cursor, and any associated states are cached upon the invocation of the close() method of the PreparedStatement or CallableStatement object(s). Statement objects can be removed from the cache when the maximum cache size is reached using a Least Recently Used (LRU) algorithm.

The implicit statement cache is disabled:

  • either by invoking setImplicitCachingEnabled(false) on the connection object

  • or by setting the ImplicitStatementCachingEnabled property to false

The Implicit cache can be purged by invoking the OracleConnection.purgeImplicitCache() method on the connection object. It closes all of the implicitly cached statements in the cache, freeing the database cursors and removing all references to JDBC objects. It does not purge the explicitly cached statements.

A Complete Example: ImplStmtCache.java

/*
 *   Implicit Statement Caching
 *
 */
// import the java.sql package
import java.sql.*;
import javax.sql.*;
import oracle.jdbc.*;
import oracle.jdbc.pool.OracleDataSource;

class ImplStmtCache
{
 public static void main (String args [])
 throws SQLException
 {
  long  start;
  long end;

  long elapsed;

  String name = null;
  OracleConnection conn = null;
  OraclePreparedStatement pstmt = null;
  OracleDataSource ods = new OracleDataSource();
  // Set the URL (user name, and password)
  String url = "jdbc:oracle:thin:scott/tiger@//localhost:1521/orcl";
  ods.setURL(url);

  String query = "select EMPNO, ENAME from EMP";
  System.out.println("PredStmt is " + query);

  //
  // Phase 1 - NO caching
  //
  // Get a connection object
  conn = (OracleConnection)ods.getConnection();
  // Set the start time
     start = System.currentTimeMillis();
  // Loop, prepare and execute the query 10 times
  for (int i = 1; i <= 10; i++) {
    pstmt = (OraclePreparedStatement)conn.prepareStatement (query);
    ResultSet rset = pstmt.executeQuery ();
    // Do not process ResultSet
    rset.close();
    pstmt.close();

  }
  // Set the End time
     end = System.currentTimeMillis();
     elapsed = end - start ;
  // Print the time taken to prepare and execute query
     System.out.println
     (" Time to prepare and execute NON CACHED query 10 times is: " +
       elapsed);
  // Close the connection
  conn.close();
  //
  // Phase 2 - Implicit Statement Caching

  //
  // Get a connection object
     conn = (OracleConnection)ods.getConnection();
  // Set the Statement cache size to 1
     conn.setStatementCacheSize(1);
  // Enable Implicit caching
     conn.setImplicitCachingEnabled(true);
  // Set the start time
     start = System.currentTimeMillis();
  // Loop, prepare and execute the query 10 times
  for (int i = 1; i <= 10; i++) {
     pstmt = (OraclePreparedStatement)conn.prepareStatement (query);
     ResultSet rset = pstmt.executeQuery ();
  // Do not process ResultSet
     rset.close();
     pstmt.close();
   }
  // Set the End time
     end = System.currentTimeMillis();
     elapsed = end - start;
  // Print the time taken to prepare and execute query
     System.out.println
       (" Time to prepare and execute CACHED query 10 times is: "
       + elapsed);
  // Close the connection
  conn.close();
 }
}

C:>javac ImplStmtCache.java
C:>java ImplStmtCache
PredStmt is select EMPNO, ENAME from EMP
 Time to prepare and execute NON CACHED query 10 times is: 611
 Time to prepare and execute CACHED query 10 times is: 20

Explicit Statement Caching

Unlike implicit statement caching, explicit statement caching is managed by the application, because it requires explicit instructions for caching and searching, using a user-defined key.

To enable explicit statement caching, you must first set the application cache size (note that the same cache can be shared by both implicit and explicit statement caching schemes, but it needs to be set only once) by:

  • Either by invoking setStatementCacheSize() on the physical connection:

    ((OracleConnection)conn).setStatementCacheSize(20);
  • Or by invoking OracleDatasource.setMaxStatements() (a size of zero disables the statement caching)

Explicit statement caching is enabled by invoking setExplicitStatementCaching(true) on the connection object.

Determining whether explicit caching is enabled is achieved by invoking getExplicitStatementCachingEnabled() , which returns true if explicit caching is enabled, or false otherwise.

A cache entry is allocated to a new SQL statement—if not already in the cache—upon the invocation of createStatement(), prepareStatement(), and prepareCall() methods. It is up to the application to search the cache for a match using the user-defined key (a Java string) specified during the closeWithKey(String), using the following:

  • getStatementWithKey(String);

  • getCallWithKey(String);

If a match is found, then the cached statement with the parameter and metadata defined in the last usage is returned; otherwise, a null value (not an exception) is returned to the application. Statement objects can be removed from the cache when the maximum cache size is reached using a Least Recently Used (LRU) algorithm.

The statements are cached upon the invocation of closeWithKey(String).

pstmt.closeWithKey("Stmt1");

Explicit statement caching is disabled by invoking the setExplicitStatementCaching(false) method on the connection object.

The explicit cache can be purged by invoking the OracleConnection.purgeExplicitCache () method on the connection object.

It closes all of the explicitly cached statements in the cache, freeing the database cursors and removing all references to JDBC objects. It does not purge the implicitly cached statements.

A Complete Example: ExplStmtCache.java

/*
 *   Explicit Statement Caching
 *
 */
// import the java.sql package
import java.sql.*;
import javax.sql.*;
import oracle.jdbc.*;
import oracle.jdbc.pool.OracleDataSource;

class ExplStmtCache
{
 public static void main (String args [])
 throws SQLException
 {
  long  start;
  long end;
  long elapsed;

  String name = null;
  OracleConnection conn = null;
  OraclePreparedStatement pstmt = null;
  OracleDataSource ods = new OracleDataSource();
  // Set the URL (user name, and password)
  String url = "jdbc:oracle:thin:scott/tiger@//localhost:1521/orcl";
  ods.setURL(url);
  String query = "select EMPNO, ENAME from EMP";
  System.out.println("PredStmt is " + query);

  //
  // Phase 1 - NO caching

  //
  // Get a connection object
  conn = (OracleConnection)ods.getConnection();
  // Set the start time
     start = System.currentTimeMillis();
  // Loop, prepare and execute the query 10 times
  for (int i = 1; i <= 10; i++) {
    pstmt = (OraclePreparedStatement)conn.prepareStatement(query);
    ResultSet rset = pstmt.executeQuery ();
    // Do not process ResultSet
    rset.close();
    pstmt.close();
   }
    // Set the End time
    end = System.currentTimeMillis();
    elapsed = end - start ;
    // Print the time taken to prepare and execute query
    System.out.println
    (" Time to prepare and execute NON CACHED query 10 times is : "
    + elapsed);

  // Close the connection
  conn.close();
  //
  // Phase 2 - Explicit Statement Caching
  //
  // Get a connection object
     conn = (OracleConnection)ods.getConnection();
  // Set the Statement cache size to 1
     conn.setStatementCacheSize(1);

  // Enable Explicit caching
     conn.setExplicitCachingEnabled(true);

  // Set the start time
     start = System.currentTimeMillis();
  // Loop, search/find or prepare and execute the query 10 times

  for (int i = 1; i <= 10; i++) {
  // Look first in the cache
   pstmt =
(OraclePreparedStatement)conn.getStatementWithKey("Stmt1");

   if (pstmt == null) { //If not in the cache then prepare again
       pstmt = (OraclePreparedStatement)conn.prepareStatement(query);
   }

   ResultSet rset = pstmt.executeQuery ();
   // Do not process ResultSet
   rset.close();
   pstmt.closeWithKey("Stmt1"); ;
   }
  // Set the End time
     end = System.currentTimeMillis();
     elapsed = end - start;
  // Print the time taken to prepare and execute query
     System.out.println
      (" Time to prepare and execute EXPLICITLY CACHED query 10 times
is " +
          elapsed);
  // Close the connection
     conn.close();
 }
}

C:>javac ExplStmtCache.java
C:>java ExplStmtCache
PredStmt is select EMPNO, ENAME from EMP
 Time to prepare and execute NON CACHED query 10 times is : 581
 Time to prepare and execute EXPLICITLY CACHED query 10 times is 10

DML Batching

The JDBC 2.0 specification defines “batch updates” as the ability to group and send a batch of DML operations to the database, in one roundtrip, resulting in optimized performance. The Oracle JDBC drivers implement the standard batch update and furnish in addition Oracle’s own “update batching.”

Standard Batch Updates

In a nutshell, the application manually builds up the batch using the addBatch() method and then explicitly sends the array of operations by invoking the executeBatch() method. All statement types, including Statement, PreparedStatement, and CallableStatement (without OUT parameter), are supported. However, in the Oracle JDBC implementation, only PreparedStatement will see a performance benefit.

Example 1

PreparedStmt pstmt =
      conn.preparedStatement("INSERT INTO PRODUCT VALUES (?,
?)");

pstmt.setInt(1, 1234);
pstmt.setString(2, "Product #1");
pstmt.addBatch();
...
pstmt.setInt(1, 1234);
pstmt.setString(2, "Product #1");
pstmt.addBatch();
...
pstmt.setInt(1, 1234);
pstmt.setString(2, "Product #1");
pstmt.addBatch();
...
int [] batchCount = pstmt.executeBatch();

Example 2: syntax

Statement stmt = conn.createStatement();
...
// First batch set
stmt.addBatch("INSERT INTO product VALUES( 1111, 'Prod1')");
stmt.addBatch("INSERT INTO product VALUES( 2222, 'Prod2')");
stmt.addBatch("INSERT INTO product VALUES( 3333, 'Prod3')");
stmt.addBatch("INSERT INTO product VALUES( 4444, 'Prod4')");
executeBatch();

//Second batch set
stmt.addBatch("insert into product values (5555,'Prod5')");
stmt.executeBatch();

// Explicit Commit
conn.commit();
ps.close();

There is no defined batch size; it is up to the application to explicitly send the batched statements to the database.

An array of int is returned to the caller; according to the JDBC specification, each entry in the array may have the following values:

  1. If the value of an array entry is greater than or equal to zero, then the batch element was processed successfully and the value is an update count indicating the number of rows in the database that were affected by the element’s execution.

  2. A value of minus 2 indicates that an element was processed successfully, but the number of affected rows is unknown.

    For PreparedStatement, upon successful execution, the Oracle JDBC implementation conforms to option 2, because the Oracle database does not currently return the number of rows updated per each array element (i.e., update count). Option 1 could be implemented at the cost of additional roundtrips.

For the generic/plain Statement, upon successful execution, the batch array contains the number of rows affected by each operation, which corresponds to option 1.

For CallableStatement, upon successful execution, the RDBMS always returns –1.

Stream types (LOB) are not supported as bind values.

Oracle Update Batching

In a nutshell, the application manually builds up the batch using the executeUpdate() method (similar to addBatch() method); however, unlike the standard update batching, Oracle’s update batching automatically sends the batch set to the RDBMS when the number of entries in the array reaches a defined batch value (defaults to 1). In other words, executeUpdate() usually queues the operations for later shipment—in this case, it returns 0—but also triggers the shipment to the RDBMS when the batch value is reached—in this case, it returns the total number of rows affected by the batch processing (all operations in the array).

Only PreparedStatements are supported. Batch values between 5 and 30 (typically 10) yield the best performance; the value is specified using:

  • setDefaultExecuteBatch(value) on the connection object for all statements

  • setExecuteBatch(value) for a PreparedStatement

Similar to the executeBatch() method in the standard update batching, the sendBatch() method on PreparedStatement may be used to manually ship the array of operations to the RDBMS; it returns the total number of rows affected by the batch processing. The Oracle update batch also ships the array to the RDBMS on statement close, connection close, and commit operations.

The getDefaultExecuteBatch() method on OracleConnection returns the batch value of a connection instance. The getExecuteBatch() method on OraclePreparedStatement returns the batch value of a specific statement.

Example:

Connection conn = ds.getConnection();

//Always disable auto-commit when using update batching
conn.setAutoCommit(false);


PreparedStmt pstmt =
      conn.preparedStatement("INSERT INTO PRODUCT VALUES (?,
?)");

//Set Batch value for this statement to 3
((OraclePreparedStatement)pstmt).setExecuteBatch (3);

pstmt.setInt(1, 1234);
pstmt.setString(2, "Product #1");
pstmt.executeUpdate();
...
pstmt.setInt(1, 1234);
pstmt.setString(2, "Product #1");
pstmt.executeUpdate();

...
pstmt.setInt(1, 1234);

pstmt.setString(2, "Product #1");
pstmt.executeUpdate(); // This one triggers the shipment

conn.commit();  // Explicit Commit
pstmt.close();

Note

Don’t mix the standard and Oracle update batching.

This concludes this chapter on the essentials of JDBC connections and statements. The next chapter addresses data access and retrieval, including, access and conversion (mapping) of various data types, result sets, rowsets, and much more.



[1] For backward compatibility, oci8: is also supported.

[2] The client and the database reside on the same host connection through named pipe protocol.

[3] The mapping is achieved at runtime through deployment descriptors.

[5] Notice that I did not mention the Transparent Application Failover (TAF) but will talk about it.

[6] A set of clustered databases (e.g., Oracle database RAC) over hardware grids (i.e., blades).

[7] As of JDBC 3.0 specification, see the javadoc for more details on the methods and their signatures.

[8] The oracle.jdbc.pool.OracleConnectionPoolDataSource implements javax.sql.ConnectionPoolDataSource, and the oracle.jdbc.pool.OraclePooledConnection class implements javax.sql.PooledConnection.

[9] A wallet is a password-protected storage for authentication and signing credentials (i.e., private keys, certificates, and trusted certificates). See the Oracle Database Advanced Security Administrator’s Guide for more details.

[10] All required files are in the ONCdemoClient.zip available on the book’s code sample page.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.224.59.192