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.
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();
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
.
The javax.sql
package introduced in JDBC 2.0 Optional Packages provides the following interfaces:
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 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
SQL> select USER from dual; USER ------------------------------ SCOTT SQL>
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.
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):
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).
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();
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");
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);
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
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.
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]
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.
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?
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. |
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:
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));
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
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);
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;
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.
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.
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
A connection attribute can be applied to a cached connection using one of the following two approaches:
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);
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
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);
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:
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);
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.
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 |
---|---|---|
|
| The value of this property is used as the user name when connecting to the database. |
|
| The value of this property is used as the password when connecting to the database. |
|
| The value of this property is used as the SID of the database. |
|
| The value of this property is used as the host name of the database. |
|
| The value of this property is used as the user name when performing an internal logon. Usually this will be SYS or SYSDBA. |
|
| The value of this property is used as the default number of rows to prefetch. |
|
| The value of this property is used as the default batch size when using Oracle-style batching. |
|
| If the value of this property is “false,” then the default setting for Statement.setEscapeProccessing is false. |
|
| 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. |
|
| Set the name of the DMS Noun that is the parent of all JDBC DMS metrics. |
|
| Set the type of the DMS Noun that is the parent of all JDBC DMS metrics. |
|
| 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. |
|
| 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. |
|
| 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. |
|
| If the value of this property is “true,” the default mode for all character data columns will be NCHAR. |
|
| 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. |
|
| 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. |
|
| If the value of this property is “true,” JDBC will include synonyms when getting information about a column. |
|
| 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. |
|
| 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. |
|
| 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. |
|
| 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. |
|
| 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. |
|
| 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. |
|
|
|
|
| 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 }
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 () }
In order to centrally manage all caches associated with all datasources within a Java VM, the Oracle JDBC driver furnishes a Mister Cache Manager.
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 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.
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.
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:
For every connection with a CONNECTION_RELEASE_HIGH
priority, invoke the callback method.
If the number of connections released back to the cache is below the MaxThresholdLimit
(the default is 80 percent of MaxLimit
):
If all the connections with a CONNECTION_RELEASE_HIGH
priority have been processed, then process the connections with CONNECTION_RELEASE_LOW
priority.
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.
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
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) Up
: The connection pool gradually releases idle connections associated with existing instances and reallocates these onto the new instance.
Instance (of Service) Down
: The connections associated with the instance are aborted and cleaned up, leaving the connection pool with sound and valid connections.
Node Down
: The 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.
Set up a multinstance Oracle Database 10g RAC database (see RAC documentation).
Virtualize the database host through a service name (see JDBC URL in section 7.1).
Enable the Implicit Connection Cache.
Configure ONS on each RAC server node.
Configure ONS on each client node (10g Release 1) or use remote subscription (10g Release 2).
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> ...]
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
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.
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 |
---|---|---|
| Starts the ONS daemon. |
|
| Stops the ONS daemon. |
|
| Verifies whether the ONS daemon is running. |
|
| Triggers a reload of the ONS configuration without shutting down the ONS daemon. | |
| Displays debug information for the ONS daemon. | |
| Prints a help summary message for onsctl. | |
| Prints a detailed help message for onsctl. |
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
$ Jmake_onc sub -c >> Recompiling onc_subscriber ... >> Executing onc_subscriber ... ONC subscriber starting
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.
** 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 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.
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.
Configure ONS and Fast Application Notification as described previously.
Enable Fast Connection Fail-over.
Code your application to catch SQLExceptions
and retry connection requests.
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).
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(); } }
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.
The following steps are required for enabling runtime connection load balancing:
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.
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.
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.
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.
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 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))))";
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 = 1
. indicates a lost connection has been detected and fail-over is starting.
FO_END = 2
. indicates a successful completion of fail-over.
FO_ABORT = 3
. indicates an unsuccessful fail-over with no option of retrying.
FO_REAUTH = 4
. indicates that a user handle has been reauthenticated.
FO_ERROR = 5
. indicates that a fail-over was temporarily unsuccessful, but the application has the opportunity to handle the error and retry.
FO_RETRY = 6
. indicates retry fail-over.
FO_EVENT_UNKNOWN = 7
. indicates a bad/unknown fail-over event.
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; } .}
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.
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.
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.
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;
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.
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;
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:>
... 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
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.
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.
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"
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
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.
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.
Let’s look at each statement type in greater detail.
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.
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.
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
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
.
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); ...
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(); ...
Dealing with Results, see Chapter 8 for more details on Result Set.
See Chapter 3for a basic but complete example of using PreparedStatement
.
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 |
---|---|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
|
|
|
| ||
|
|
|
| ||
|
|
|
| ||
|
|
| |||
|
|
|
|
|
|
|
|
|
|
|
|
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) |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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.
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.
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).
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.
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;");
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); ...
Similar to PreparedStatement
, you must set the value(s) for input parameters:
cstmt1.setString(2, "aadfadfad"); cstmt1.setInt(3, 125); ...
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(); ...
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 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)
).
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
Returned Object Type | |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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();
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.
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:
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.
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
.
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.
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 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:
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.
/* * 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
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.
/* * 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
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.”
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.
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();
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:
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.
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.
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:
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.
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();
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.
18.224.59.192