Oracle® C++ Call Interface Programmer's Guide, 11g Release 1 (11.1) Part Number B28390-01 |
|
|
View PDF |
This chapter describes the basics of developing C++ applications using Oracle C++ Call Interface (OCCI) to work with data stored in relational databases.
This chapter contains these topics:
You have a number of different options with regard to how your application connects to the database.
All OCCI processing takes place in the context of the Environment
class. An OCCI environment provides application modes and user-specified memory management functions. Example 3-1 illustrates how to create an OCCI environment.
All OCCI objects created with the create
xxx
()
methods (connections, connection pools, statements) must be explicitly terminated. When appropriate, you must also explicitly terminate the environment. Example 3-2 shows how you terminate an OCCI environment.
In addition, an OCCI environment should have a scope that is larger than the scope of the following object types created in the context of that environment: Agent
, Bytes
, Date
, Message
, IntervalDS
, IntervalYM
, Subscription
, and Timestamp
. This rule does not apply to BFile
, Blob
, and Clob
objects, as demonstrated in Example 3-3.
Example 3-3 How to Use Environment Scope with Blob Objects
const string userName = "HR"; const string password = "password"; const string connectString = ""; Environment *env = Environment::createEnvironment(); { Connection *conn = env->createConnection( userName, password, connectString); Statement *stmt = conn->createStatement( "SELECT blobcol FROM mytable"); ResultSet *rs = stmt->executeQuery(); rs->next(); Blob b = rs->getBlob(1); cout << "Length of BLOB : " << b.length(); ... stmt->closeResultSet(rs); conn->terminateStatement(stmt); env->terminateConnection(conn); } Environment::terminateEnvironment(env);
If the application requires access to objects in the global scope, such as static or global variables, these objects must be set to NULL
before the environment is terminated. In the preceding example, if b
was a global variable, a b.setNull()
call has to be made prior to the terminateEnvironment()
call.
You can use the mode parameter of the c
reateEnvironmnt()
method to specify that your application:
Runs in a threaded environment (THREADED_MUTEXED
or THREADED_UNMUTEXED
)
Uses objects (OBJECT
)
The mode can be set independently in each environment.
The Environment
class is the factory class for creating Connection
objects. You first create an Environment
instance, and then use it to enable users to connect to the database by means of the createConnection()
method.
Example 3-4 creates an environment instance and then uses it to create a database connection for a database user HR
with the appropriate password.
Example 3-4 How to Create an Environment and then a Connection to the Database
Environment *env = Environment::createEnvironment(); Connection *conn = env->createConnection("HR", "password");
You must use the terminateConnection()
method shown in the following code example to explicitly close the connection at the end of the working session. In addition, the OCCI environment should be explicitly terminated.
You should remember that all objects (Ref
s, Bfile
s, Producer
s, Consumer
s, and so on) created or named within a Connection
instance must be within the inner scope of that instance; the scope of these objects must be explicitly terminated before the Connection
is terminated. Example 3-5 demonstrates how to terminate the connection and the environment.
This section discusses how to use the connection pooling feature of OCCI. The information covered includes the following topics:
The primary difference between the two is that StatelessConnectionPool
s are used for applications that don't depend on state considerations; these applications can benefit from performance improvements available through use of pre-authenticated connections.
For many middle-tier applications, connections to the database should be enabled for a large number of threads. Because each thread exists for a relatively short time, opening a connection to the database for every thread would be inefficient use of connections, and would result in poor performance.
By employing the connection pooling feature, your application can create a small set of connections available to a large number of threads, enabling you to use database resources very efficiently.
To create a connection pool, you use the createConnectionPool() method, as demonstrated in Example 3-6.
Example 3-6 The createConnectionPool() Method
virtual ConnectionPool* createConnectionPool( const string &poolUserName, const string &poolPassword, const string &connectString ="", unsigned int minConn =0, unsigned int maxConn =1, unsigned int incrConn =1) = 0;
The following parameters are used in Example 3-6:
poolUserName
: The owner of the connection pool
poolPassword
: The password to gain access to the connection pool
connectString
: The database name that specifies the database server to which the connection pool is related
minConn
: The minimum number of connections to be opened when the connection pool is created
maxConn
: The maximum number of connections that can be maintained by the connection pool. When the maximum number of connections are open in the connection pool, and all the connections are busy, an OCCI method call that needs a connection waits until it gets one, unless setErrorOnBusy()
was called on the connection pool
incrConn
: The additional number of connections to be opened when all the connections are busy and a call needs a connection. This increment is implemented only when the total number of open connections is less than the maximum number of connections that can be opened in that connection pool.
Example 3-7 demonstrates how you can create a connection pool.
Example 3-7 How to Create a Connection Pool
const string connectString = ""; unsigned int maxConn = 5; unsigned int minConn = 3; unsigned int incrConn = 2; ConnectionPool *connPool = env->createConnectionPool( poolUserName, poolPassword, connectString, minConn, maxConn, incrConn);
You can also configure all these attributes dynamically. This lets you design an application that has the flexibility of reading the current load (number of open connections and number of busy connections) and tune these attributes appropriately. In addition, you can use the setTimeOut()
method to time out the connections that are idle for more than the specified time. The OCCI terminates idle connections periodically so as to maintain an optimum number of open connections.
There is no restriction that one environment must have only one connection pool. There can be multiple connection pools in a single OCCI environment, and these can connect to the same or different databases. This is useful for applications requiring load balancing.
If you authorize the connection pool user to act as a proxy for other connections, then no password is required to log in database users who use one of the connections in the connection pool.
A proxy connection can be created by using either of the following two versions of the createProxyConnection() method, demonstrated in Example 3-8.
Example 3-8 The createProxyConnection() Method
ConnectionPool->createProxyConnection( const string &username, Connection::ProxyType proxyType = Connection::PROXY_DEFAULT);
or
ConnectionPool->createProxyConnection( const string &username, string roles[], int numRoles, Connection::ProxyType proxyType = Connection::PROXY_DEFAULT);
The following parameters are used in the previous method example:
roles[]
: The roles array specifies a list of roles to be activated after the proxy connection is activated for the client
Connection::ProxyType proxyType = Connection::PROXY_DEFAULT
: The enumeration Connection::ProxyType
lists constants representing the various ways of achieving proxy authentication. PROXY_DEFAULT
is used to indicate that name
represents a database username and is the only proxy authentication mode currently supported.
Stateless Connection Pooling is specifically designed for use in applications that require short connection times and don't need to deal with state considerations. The primary benefit of Stateless Connection Pooling is increased performance, since the time consuming connection and authentication protocols are eliminated.
Stateless Connection Pools create and maintain a group of stateless, authenticated connection to the database that can be used by multiple threads. Once a thread finishes using its connection, it should release the connection back to the pool. If no connections are available, new ones are generated. Thus, the number of connections in the pool can increase dynamically.
Some of the connections in the pool may be tagged with specific properties. The user may request a default connection, set certain attributes, such as Globalization Support settings, then tag it and return it to the pool. When a connection with same attributes is needed, a request for a connection with the same tag can be made, and one of several connections in the pool with the same tag can be reused. The tag on a connection can be changed or reset.
Proxy connections may also be created and maintained through the Stateless Connection Pooling interface.
Stateless connection pooling improves the scalability of the mid-tier applications by multiplexing the connections. However, connections from a StatelessConnectionPool
should not be used for long transactions, as holding connections for long periods leads to reduced concurrency.
Caution:
OCCI will not check for the correctness of the connection-tag pair. You are responsible for ensuring that connections with different client-side properties do not have the same tag.
OCCI does not remove the state of the connection, either through commit or a rollback operation before releasing the connection. If a state remains with a connection when it is released back to the pool, it will remain when the connection is reused. You are responsible for removing the state of the connection before releasing it back to the pool.
There are two types of stateless connection pools:
A homogeneous pool is one in which all the connections will be authenticated with the username and password provided at the time of creation of the pool. Therefore, all connections will have the same authentication context. Proxy connections are not allowed in such pools.
Different connections can be authenticated by different usernames in heterogeneous pools. Proxy connections can also exist in heterogeneous pools, provided the necessary privileges for creating them are granted on the server.
Example 3-9 illustrates a basic usage scenario for connection pools. Example 3-10 presents the usage scenario for creating and using a homogeneous stateless connection pool, while Example 3-11 covers the use of heterogeneous pools.
Example 3-9 How to Use a StatelessConnectionPool
Because the pool size is dynamic, in response to changing user requirements, up to the specified maximum number of connections. Assume that a stateless connection pool is created with the following parameters:
minConn = 5
incrConn = 2
maxConn = 10
Five connections are opened when the pool is created:
openConn = 5
Using get[AnyTagged][Proxy]Connection()
methods, the user consumes all 5 open connections:
openConn = 5
busyConn = 5
When the user wants another connection, the pool will open 2
new connections and return one of them to the user
openConn = 7
busyConn = 6
The upper limit for the number of connections that can be pooled is maxConn
specified at the time of creation of the pool.
The user can also modify the pool parameters after the pool is created using the call to setPoolSize()
method.
If a heterogeneous pool is created, the incrConn
and minConn
arguments are ignored.
Example 3-10 How to Create and Use a Homogeneous Stateless Connection Pool
To create a homogeneous stateless connection pool, follow these basic steps and pseudocode commands:
Create a stateless connection pool in the HOMOGENEOUS
mode of the Environment
with a createStatelessConnectionPool() call.
StatelessConnectionPool *scp = env->createStatelessConnectionPool( username, passwd, connectString, maxCon, minCon, incrCon, StatelessConnectionPool::HOMOGENEOUS );
Get a new or existing connection from the pool by calling the getConnection() method.
Connection *conn=scp->getConnection(tag);
During the execution of this call, the pool is searched for a connection with a matching tag. If such a connection exists, it is returned to the user. Otherwise, an untagged connection authenticated by the pool username and password is returned.
Alternatively, you can obtain a connection with getAnyTaggedConnection() call. It will return a connection with a non-matching tag if neither a matching tag or NULL
tag connections are available. You should verify the tag returned by a getTag() call on Connection
.
Connection *conn=scp->getAnyTaggedConnection(tag); string tag=conn->getTag();
Use the connection.
Release the connection to the StatelessConnectionPool
through the releaseConnection() call.
scp->releaseConnection(conn, tag);
An empty tag, "", untags the Connection
.
You have an option of retrieving the connection from the StatelessConnectionPool
using the same tag
parameter value in a getConnection() call.
Connection *conn=scp->getConnection(tag);
Instead of returning the Connection
to the StatelessConnectionPool,
you may wish to destroy it using the terminateConnection() call.
scp->terminateConnection(conn);
Destroy the pool through aterminateStatelessConnectionPool() call on the Environment
object.
env->terminateStatelessConnectionPool(scp);
Example 3-11 How to Create and Use a Heterogeneous Stateless Connection Pool
To create a heterogeneous stateless connection pool, follow these basic steps and pseudocode commands:
Create a stateless connection pool in the HETEROGENEOUS
mode of the Environment
with a createStatelessConnectionPool() call.
StatelessConnectionPool *scp = env->createStatelessConnectionPool( username, passwd, connectString, maxCon, minCon, incrCon, StatelessConnectionPool::HETEROGENEOUS);
Get a new or existing connection from the pool by calling the getConnection() method of the StatelessConnectionPool
that is overloaded for the heterogeneous pool option.
Connection *conn=scp->getConnection(username, passwd, tag);
During the execution of this call, the heterogeneous pool is searched for a connection with a matching tag. If such a connection exists, it is returned to the user. Otherwise, an appropriately authenticated untagged connection with a NULL
tag is returned.
Alternatively, you can obtain a connection with getAnyTaggedConnection() call that has been overloaded for heterogeneous pools. It will return a connection with a non-matching tag if neither a matching tag or NULL
tag connections are available. You should verify the tag returned by a getTag() call on Connection
.
Connection *conn=scp->getAnyTaggedConnection(username, passwd, tag); string tag=conn->getTag();
You may also wish to use proxy connections by getProxyConnection() or getAnyTaggedProxyConnection() calls on the StatelessConnectionPool
.
Connection *pconn = scp->getProxyConnection(proxyName, roles{}, nuRoles, tag, proxyType); Connection *pconn = scp->getAnyTaggedProxyConnection( proxyName, tag, proxyType);
Use the connection.
Release the connection to the StatelessConnectionPool
through the releaseConnection() call.
scp->releaseConnection(conn, tag);
An empty tag, "", untags the Connection
.
You have an option of retrieving the connection from the StatelessConnectionPool
using the same tag
parameter value in a getConnection() call.
Connection *conn=scp->getConnection(tag);
Instead of returning the Connection
to the StatelessConnectionPool,
you may wish to destroy it using the terminateConnection()terminateStatelessConnectionPool() call.
scp->terminateConnection(conn);
Destroy the pool through a terminateStatelessConnectionPool() call on the Environment
object.
env->terminateStatelessConnectionPool(scp);
Enterprise-level applications must typically handle a high volume of simultaneous user sessions that are implemented as persistent connections to the database. The memory overhead of creating and managing these connections has significant implications for the performance of the database.
Database Resident Connection Pooling solves the problem of too many persistent connections by providing a pool of dedicated servers for handling a large set of application connections, thus enabling the database to scale to tens of thousands of simultaneous connections. It significantly reduces the memory footprint on the database tier and increases the scalability of both the database and middle tiers. Database Resident Connection Pooling is designed for architectures with multi-process application servers and multiple middle tiers that cannot accommodate connection pooling in the middle tier.
Database Resident Connection Pooling architecture closely follows the default dedicated model for connecting to an Oracle Database instance; however, it removes the overhead of assigning a specific server to each connection. On the server tier, a majority of connections are inactive at any given time, and each of these connections consumes memory. Because of this, database systems that support high connection volumes face the risk of quickly exhausting all available memory. Database Resident Connection Pooling allows a connection to use a dedicated server, which combines an Oracle server process and a user session. Once the connection becomes inactive, it returns its resources to the pool, for use by similar connections.
In multithreaded middle tiers that are capable of comprehensive connection pooling, the issue of unused connections is somewhat different. As the number of middle tiers increases, each middle tier privately holds a number of connections to the database; these connections cannot be shared with other middle tiers. Locating the connection pool on the database instead enables the sharing of connections across similar clients.
Database Resident Connection Pooling supports password-based authentication, statement caching, tagging, and Fast Application Notification. You can also use client-side stateless connection pooling in conjunction with the database resident connection pooling.
Note that clients that hold connections from the database resident connection pool are persistently connected to a background Connection Broker process. The Connection Broker implements the pool functionality and multiplexes inbound client connections to a pool of dedicated server processes. Clients that do not use the connection pool use dedicated server processes instead.
See Also:
Oracle Database Concepts for details about the architecture of Database Resident Connection Pooling
Oracle Database Administrator's Guide for details on configuring Database Resident Connection Pooling
Oracle Database PL/SQL Packages and Types Reference, for the DBMS_CONNECTION_POOL
package
To implement database resident connection pooling, it must first be enabled on the system by a user with SYSDBA
privileges. See Example 3-12 for steps necessary to initiate and maintain a database resident connection pool.
Example 3-12 How to Administer the Database Resident Connection Pools
A user with SYSDBA
privileges must perform the next steps.
Connect to the database.
SQLPLUS / AS SYSDBA
[Optional] Configure the parameters of the database resident connection pool. The default values of a pool are set in the following way:
DBMS_CONNECTION_POOL.CONFIGURE_POOL( 'SYS_DEFAULT_CONNECTION_POOL', MIN=>10, MAX=>200);
[Optional] Alter specific parameters of the database resident connection pool without affecting other parameters.
DBMS_CONNECTION_POOL.ALTER_PARAM( 'SYS_DEFAULT_CONNECTION_POOL', 'INACTIVITY_TIMEOUT', 10);
Start the connection pool. After this step, the connection pool is available to all qualified clients.
DBMS_CONNECTION_POOL.START_POOL( 'SYS_DEFAULT_CONNECTION_POOL');
[Optional] Change the parameters of the database resident connection pool.
DBMS_CONNECTION_POOL.ALTER_PARAM( 'SYS_DEFAULT_CONNECTION_POOL', 'MAXSIZE', 20);
[Optional] The configuration of the connection pool can be reset to default values.
DBMS_CONNECTION_POOL.RESTORE_DEFAULTS ( 'SYS_DEFAULT_CONNECTION_POOL');
Stop the pool. Note that pool information is persistent: stopping the pool does not destroy the pool name and configuration parameters.
DBMS_CONNECTION_POOL.STOP_POOL();
Note that in RAC configurations, the database resident connection pool starts on all configured nodes. If the pool is not stopped, the starting configuration is persistent across instance restarts: the pool is started automatically when the instance comes up.
To use database resident connection pooling, you must specify the connection class and connection purity. If the application requests a connection that cannot be potentially tainted with prior connection state, it must specify purity as NEW
; Oracle recommends this approach if clients from different geographic locale settings share the same database instance. When the application can use a previously used connection, the purity should be set to SELF
. In conjunction with connection class and purity specifications, you can also use an application-specific tags to choose a previously used connection that has the desired state. The default connection pool name, as demonstrated in Example 3-12, is SYS_DEFAULT_CONNECTION_POOL.
This feature overloads StatelessConnectionPool Class and Environment Class interfaces for retrieving a connection (getConnection()
and getProxyConnection()
) by adding the parameters that specify connection class and purity. Every connection request outside of a client-side connection pool has a default purity of NEW
. Connection requests in the context of a client-side connection pool have a default purity of SELF
.
Example 3-13 How to Get a Connection from a Database Resident Connection Pool
conn1 = env->createConnection (/*username */"hr", /*password*/ "password", /* database*/ "inst1_cmon", /* connection class */"TESTCC", /* purity */Connection::SELF); stmt1 = conn1->createStatement("select count(*) from emp"); rs=stmt1->executeQuery(); while (rs->next()) { int num = rs->getInt(1); sprintf((char *)tmp, "%d", num); cout << tmp << endl; } stmt1->closeResultSet(rs); conn1->terminateStatement(stmt1); env->terminateConnection(conn1);
Example 3-14 Using Client-Side Pool and Server-Side Pool
StatelessConnectionPool *scPool; OCCIConnection *conn1, *conn2; scPool = env->createStatelessConnectionPool (poolUserName, poolPassword, connectString, maxConn, minConn, incrConn, StatelessConnectionPool::HOMOGENEOUS); conn1= scPool->getConnection( /* Connection class name */"TESTCC", /* Purity */ Connection::SELF); /* or, for proxy coonnections */ conn2= scPool->getProxyConnection(/* username*/ "HR_PROXY", /*Connection class */"TESTCC", /* Purity */Connection::SELF); /* or, for getting a tagged connection */ conn3 = scPool->getConnection(/*connection class */"TESTCC", /*purity*/ Connection::SELF, /*tag*/ "TESTTAG"); /* Releasing a tagged connection is the same as is done presently */ scPool->releaseConnection(conn3, "TESTTAG"); /* In order to specify purity as new */ conn4 = scPool->getConnection(/* connection class */"TESTCC",/* purity of new */ Connection::NEW); /* Get a connection using username and password */ conn5 = scPool->getConnection (username, password,"TESTCC", Connection::SELF); /* Using roles when asking for a connection */ conn6 = scPool->getProxyConnection (username, roles, nRoles,"TESTCC", Connection::SELF); ... /* The other code continues as is...writing for the sake of clarity */ ... stmt1=conn1->createStatement ("INSERT INTO emp values (:c1, :c2)"); stmt1->setInt(1, thrid); stmt1->setString(2, "Test"); int count = stmt1->executeUpdate (); conn1->commit(); conn1->terminateStatement(stmt1); /* Release the connection */ scPool->releaseConnection (conn1); ... env->terminateStatelessConnectionPool (scPool);
SQL is the industry-wide language for working with relational databases. In OCCI you execute SQL commands through the Statement
class.
To create a Statement
object, call the createStatement()
method of the Connection
object, as demonstrated in Example 3-15,
Once you have created a Statement
object, execute SQL commands by calling the execute(), executeUpdate(), executeArrayUpdate(), or executeQuery() methods on the Statement
object. These methods are used for the following purposes:
execute() executes all nonspecific statement types
executeUpdate() executes DML and DDL statements
executeArrayUpdate() executes multiple DML statements
executeQuery() executes a query
Example 3-16 demonstrates how you can create a database table using the executeUpdate() method.
Similarly, you can execute a SQL INSERT
statement by invoking the executeUpdate() method, as demonstrated in Example 3-17.
Example 3-17 How to Add Records Using the executeUpdate() Method
stmt->executeUpdate("INSERT INTO shopping_basket VALUES('MANGO', 3)");
The executeUpdate() method returns the number of rows affected by the SQL statement.
See Also: $ORACLE_HOME/rdbms/demo for a code example that demonstrates how to perform insert, select, update, and delete operations on the table row. |
You can reuse a Statement
object to execute SQL statements multiple times. To repeatedly execute the same statement with different parameters, you should specify the statement by the setSQL() method of the Statement
object, as demonstrated in Example 3-18.
Example 3-18 How to Specify a SQL Statement Using the setSQL() Method
stmt->setSQL("INSERT INTO shopping_basket VALUES(:1,:2)");
You may now execute this INSERT
statement as many times as required. If at a later time you wish to execute a different SQL statement, you simply reset the statement object, as demonstrated in Example 3-19.
Example 3-19 How to Reset a SQL Statement Using the setSQL() Method
stmt->setSQL("SELECT * FROM shopping_basket WHERE quantity >= :1");
By using the setSQL() method, OCCI statement objects and their associated resources are not allocated or freed unnecessarily. To retrieve the contents of the current statement object at any time, use the getSQL() method.
You should explicitly terminate and deallocate a Statement
object using the terminateStatement() method, as demonstrated in Example 3-20.
There are three types of SQL statements in the OCCI environment:
Standard Statements use SQL commands with specified values
Parameterized Statements have parameters, or bind variables
Callable Statements call stored PL/SQL procedures and functions
The methods of the Statement Class are subdivided into those applicable to all statements, to parameterized statements, and to callable statements. Standard statements are a superset of parameterized statements, and parameterized statements are a superset of callable statements.
Both Example 3-16 and Example 3-17 demonstrate standard statements in which you must explicitly define the values of the statement. In Example 3-16, the CREATE
TABLE
statement specifies the name of the table shopping_basket
. In Example 3-17, the INSERT
statement stipulates the values that will be inserted into the table, ('MANGO', 3)
.
You can execute the same statement with different parameters by setting placeholders for the input variables of the statement. These statements are referred to as parameterized statements because they can accept parameter input from a user or a program.
If you want to execute an INSERT
statement with different parameters, you must first specify the statement by the setSQL() method of the Statement
object, as demonstrated in Example 3-18.
You then call the set
xxx
()
methods to specify the parameters, where xxx
stands for the type of the parameter. Provided that the value of the statement object is "INSERT INTO shopping_basket VALUES(:1,:2)"
, as specified in Example 3-18, you can use the code in Example 3-21 to invoke the setString() method and setInt() method to input the values of these types into the first and second parameters, and the executeUpdate() method to insert the new row into the table.You can re-use the statement object by re-setting the parameters and once again calling the executeUpdate() method. If your application is executing the same statement repeatedly, you should avoid changing the input parameter types because this initiates a rebind operation, and affects application performance.
Example 3-21 How to Use setxxx() Methods to Set Individual Column Values
stmt->setString(1, "Banana"); // value for first parameter stmt->setInt(2, 5); // value for second parameter stmt->executeUpdate(); // execute statement ... stmt->setString(1, "Apple"); // value for first parameter stmt->setInt(2, 9); // value for second parameter stmt->executeUpdate(); // execute statement
PL/SQL stored procedures, as their name suggests, are procedures that are stored on the database server for reuse by an application. In OCCI, a callable statement is a call to a procedure which contains other SQL statements.
If you wish to call a procedure countGroceries()
, that returns the quantity of a specified kind of fruit, you must first specify the input parameters of a PL/SQL stored procedure through the set
XXX
()
methods of the Statement
class, as demonstrated in Example 3-22.
Example 3-22 How to Specify the IN Parameters of a PL/SQL Stored Procedure
stmt->setSQL("BEGIN countGroceries(:1, :2); END:"); int quantity; stmt->setString(1, "Apple"); // specify the first (IN) parameter of procedure
However, before calling a stored procedure, you need to specify the type and size of any OUT
parameters by calling the registerOutParam() method, as demonstrated in Example 3-23. For IN/OUT
parameters, use the set
XXX
()
methods to pass in the parameter, and get
XXX
()
methods to retrieve the results.
Example 3-23 How to Specify OUT Parameters of a PL/SQL Stored Procedure
stmt->registerOutParam(2, Type::OCCIINT, sizeof(quantity)); // specify type and size of the second (OUT) parameter
You now execute the statement by calling the procedure:
stmt->executeUpdate(); // call the procedure
Finally, you obtain the output parameters by calling the relevant get
xxx() method:
quantity = stmt->getInt(2); // get value of the second (OUT) parameter
A PL/SQL stored procedure executed through a callable statement can have array of values as parameters. The number of elements in the array and the dimension of elements in the array are specified through the setDataBufferArray()
method.
The following example shows the setDataBufferArray()
method:
void setDataBufferArray( unsigned int paramIndex, void *buffer, Type type, ub4 arraySize, ub4 *arrayLength, sb4 elementSize, ub2 *elementLength, sb2 *ind = NULL, ub2 *rc = NULL);
The following parameters are used in the previous method example:
paramIndex
: Parameter number
buffer
: Data buffer containing an array of values
Type
: Type of data in the data buffer
arraySize
: Maximum number of elements in the array
arrayLength
: Number of elements in the array
elementSize
: Size of the current element in the array
elementLength
: Pointer to an array of lengths. elementLength[i]
has the current length of the i
th element of the array
ind
: Indicator information
rc
: Returns code
OCCI supports a streaming interface for insertion and retrieval of very large columns by breaking the data into a series of small chunks. This approach minimizes client-side memory requirements. This streaming interface can be used with parameterized statements such as SELECT
and various DML commands, and with callable statements in PL/SQL blocks. The datatypes supported by streams are BLOB
, CLOB
, LONG
, LONG RAW
, RAW
, and VARCHAR2
.
Streamed data is of three kinds:
A writable stream corresponds to a bind variable in a SELECT
/DML statement or an IN
argument in a callable statement.
A readable stream corresponds to a fetched column value in a SELECT
statement or an OUT
argument in a callable statement.
A bidirectional stream corresponds to an IN/OUT
bind variable.
Methods of the StreamClass support the stream interface.
The getStream() method of the Statement Class returns a stream object that supports reading and writing for DML and callable statements:
For writing, it passes data to a bind variable or to an IN
or IN/OUT
argument
For reading, it fetches data from an OUT
or IN/OUT
argument
The getStream() method of the ResultSet Class returns a stream object that can be used for reading data.
The status()
method of these classes determines the status of the streaming operation.
To bind data in a streaming mode, follow these steps and review Example 3-24:
Create a SELECT
/DML
or PL/SQL statement with appropriate bind placeholders.
Call the setBinaryStreamMode() or setCharacterStreamMode() method of the Statement Class for each bind position that will be used in the streaming mode. If the bind position is a PL/SQL IN
or IN/OUT
argument type, indicate this by calling the three-argument versions of these methods and setting the inArg
parameter to TRUE
.
Execute the statement; the status() method of the Statement Class will return NEEDS_STREAM_DATA
.
Obtain the stream object through a getStream() method of the Statement Class.
Use writeBuffer() and writeLastBuffer() methods of the StreamClass to write data.
Close the stream with closeStream() method of the Statement Class.
After all streams are closed, the status() method of the Statement Class will change to an appropriate value, such as UPDATE_COUNT_AVAILABLE
.
Example 3-24 How to Bind Data in a Streaming Mode
Statement *stmt = conn->createStatement( "Insert Into testtab(longcol) values (:1)"); //longcol is LONG type column stmt->setCharacterStreamMode(1, 100000); stmt->executeUpdate(); Stream *instream = stmt->getStream(1); char buffer[1000]; instream->writeBuffer(buffer, len); //write data instream->writeLastBuffer(buffer, len); //repeat stmt->closeStream(instream); //stmt->status() is //UPDATE_COUNT_AVAILABLE Statement *stmt = conn->createStatement("BEGIN testproc(:1); END;"); //if the argument type to testproc is IN or IN/OUT then pass TRUE to //setCharacterStreamMode or setBinaryStreamMode stmt->setBinaryStreamMode(1, 100000, TRUE);
To fetch data from a streaming mode, follow these steps and review Example 3-25:
Create a SELECT
/DML statement with appropriate bind placeholders.
Call the setBinaryStreamMode() or setCharacterStreamMode() method of the Statement Class for each bind position into which data will be retrieved from the streaming mode.
Execute the statement; the status() method of the Statement Class will return STREAM_DATA_AVAILABLE
.
Obtain the stream object through a getStream() method of the Statement Class.
Use readBuffer() and readLastBuffer() methods of the StreamClass to read data.
Close the stream with closeStream() method of the Statement Class.
Example 3-25 How to Fetch Data in a Streaming Mode Using PL/SQL
Statement *stmt = conn->createStatement("BEGIN testproc(:1); END;"); //argument 1 is OUT type stmt->setCharacterStreamMode(1, 100000); stmt->execute(); Stream *outarg = stmt->getStream(1); //use Stream::readBuffer/readLastBuffer to read data
Executing SQL Queries and Example 3-27 provide an explanation of how to use the streaming interface with result sets.
If you have to work with multiple read and write streams, you have to ensure that the read or write of one stream is completed prior to reading or writing on another stream. To determine stream position, use the getCurrentStreamParam() method of the Statement Class or the getCurrentStreamColumn() method of the ResultSet Class. The status() method of the StreamClass will return READY_FOR_READ
if there is data in the stream available for reading, or it will return INACTIVE
if all the data has been read, as described in Table 13-44. The application can then read the next streaming column. Example 3-26 demonstrates how to read and write with two concurrent streams.
Example 3-26 How to Read and Write with Multiple Streams
Statement *stmt = conn->createStatement( "Insert into testtab(longcol1, longcol2) values (:1,:2)"); //longcol1 AND longcol2 are 2 columns inserted in streaming mode stmt->setBinaryStreamMode(1, 100000); stmt->setBinaryStreamMode(2, 100000); stmt->executeUpdate(); Stream *col1 = stmt->getStream(1); Stream *col2 = stmt->getStream(2); col1->writeBuffer(buffer, len); //first stream ... //complete writing coll stream col1->writeLastBuffer(buffer, len); //finish first stream and move to col2 col2->writeBuffer(buffer, len); //second stream //reading multiple streams stmt = conn->createStatement("select longcol1, longcol2 from testtab"); ResultSet *rs = stmt->executeQuery(); rs->setBinaryStreamMode(1, 100000); rs->setBinaryStreamMode(2, 100000); while (rs->next()) { Stream *s1 = rs->getStream(1) while (s1->status() == Stream::READY_FOR_READ) { s1->readBuffer(buffer,size); //process } //first streaming column done rs->closeStream(s1); //move onto next column. rs->getCurrentStreamColumn() will return 2 Stream *s2 = rs->getStream(2) while (s2->status() == Stream::READY_FOR_READ) { s2->readBuffer(buffer,size); //process } //close the stream rs->closeStream(s2); }
Note:
It is not possible to use these streaming interfaces together with thesetDataBuffer()
method in the same Statement
and ResultSet
objects.While you can issue the executeUpdate
method repeatedly for each row, OCCI provides an efficient mechanism for sending data for multiple rows in a single network round-trip. To do this, use the addIteration()
method of the Statement
class to perform batch operations that modify a different row with each iteration.
To execute INSERT
, UPDATE
, and DELETE
operations iteratively, you must:
Set the maximum number of iterations
Set the maximum parameter size for variable length parameters
For iterative execution, first specify the maximum number of iterations that would be done for the statement by calling the setMaxIterations()
method:
Statement->setMaxIterations(int maxIterations);
You can retrieve the current maximum iterations setting by calling the getMaxIterations()
method.
If the iterative execution involves variable length datatypes, such as string
and Bytes
, then you must set the maximum parameter size so that OCCI can allocate the maximum size buffer:
Statement->setMaxParamSize(int parameterIndex, int maxParamSize);
You do not need to set the maximum parameter size for fixed length datatypes, such as Number
and Date
, or for parameters that use the setDataBuffer()
method.
You can retrieve the current maximum parameter size setting by calling the getMaxParamSize()
method.
Once you have set the maximum number of iterations and (if necessary) the maximum parameter size, iterative execution using a parameterized statement is straightforward, as shown in the following example:
stmt->setSQL("INSERT INTO basket_tab VALUES(:1, :2)"); stmt->setString(1, "Apples"); // value for first parameter of first row stmt->setInt(2, 6); // value for second parameter of first row stmt->addIteration(); // add the iteration stmt->setString(1, "Oranges"); // value for first parameter of second row stmt->setInt(1, 4); // value for second parameter of second row stmt->executeUpdate(); // execute statement
As shown in the example, you call the addIteration()
method after each iteration except the last, after which you invoke executeUpdate()
method. Of course, if you did not have a second row to insert, then you would not need to call the addIteration()
method or make the subsequent calls to the set
xxx()
methods.
Iterative execution is designed only for use in INSERT
, UPDATE
and DELETE
operations that use either standard or parameterized statements. It cannot be used for callable statements and queries.
The datatype cannot be changed between iterations. For example, if you use setInt()
for parameter 1
, then you cannot use setString()
for the same parameter in a later iteration.
SQL query statements allow your applications to request information from a database based on any constraints specified. A result set is returned as a result of a query.
Execution of a database query puts the results of the query into a set of rows called the result set. In OCCI, a SQL SELECT
statement is executed by the executeQuery
method of the Statement
class. This method returns an ResultSet
object that represents the results of a query.
ResultSet *rs = stmt->executeQuery("SELECT * FROM basket_tab");
Once you have the data in the result set, you can perform operations on it. For example, suppose you wanted to print the contents of this table. The next()
method of the ResultSet
is used to fetch data, and the get
xxx()
methods are used to retrieve the individual columns of the result set, as shown in the following code example:
cout << "The basket has:" << endl; while (rs->next()) { string fruit = rs->getString(1); // get the first column as string int quantity = rs->getInt(2); // get the second column as int cout << quantity << " " << fruit << endl; }
The next()
and status()
methods of the ResultSet
class return Status
, as defined in Table 13-37.
If data is available for the current row, then the status is DATA_AVAILABLE
. After all the data has been read, the status changes to END_OF_FETCH
. If there are any output streams to be read, then the status is STREAM_DATA_AVAILABLE
, until all the streamed data are read successfully.
Example 3-27 illustrates how to fetch streaming data into a result set, while section "Streamed Reads and Writes" provides the general background.
Example 3-27 How to Fetch Data in Streaming Mode Using ResultSet
char buffer[4096]; ResultSet *rs = stmt->executeQuery ("SELECT col1, col2 FROM tab1 WHERE col1 = 11"); rs->setCharacterStreamMode(2, 10000); while (rs->next ()) { unsigned int length = 0; unsigned int size = 500; Stream *stream = rs->getStream (2); while (stream->status () == Stream::READY_FOR_READ) { length += stream->readBuffer (buffer +length, size); } cout << "Read " << length << " bytes into the buffer" << endl; }
The IN
bind variables can be used with queries to specify constraints in the WHERE
clause of a query. For example, the following program prints only those items that have a minimum quantity of 4
:
stmt->setSQL("SELECT * FROM basket_tab WHERE quantity >= :1"); int minimumQuantity = 4; stmt->setInt(1, minimumQuantity); // set first parameter ResultSet *rs = stmt->executeQuery(); cout << "The basket has:" << endl; while (rs->next()) cout << rs->getInt(2) << " " << rs->getString(1) << endl;
Although the ResultSet
method retrieves data one row at a time, the actual fetch of data from the server need not entail a network round-trip for each row queried. To maximize the performance, you can set the number of rows to prefetch in each round-trip to the server.
You effect this either by setting the number of rows to be prefetched through the setPrefetchRowCount(
)
method, or by setting the memory size to be used for prefetching through the setPrefetchMemorySize()
method.
If you set both of these attributes, then the specified number of rows are prefetched unless the specified memory limit is reached first. If the specified memory limit is reached first, then the prefetch returns as many rows as will fit in the memory space defined by the call to the setPrefetchMemorySize()
method.
By default, prefetching is turned on, and the database fetches an extra row all the time. To turn prefetching off, set both the prefetch row count and memory size to 0
.
Note:
Prefetching is not in effect ifLONG
columns are part of the query. Queries containing LOB
columns can be prefetched, because the LOB
locator, rather than the data, is returned by the query.When you know that you need to execute a DML operation, you use the executeUpdate
method. Similarly, when you know that you need to execute a query, you use executeQuery()
method.
If your application needs to allow for dynamic events and you cannot be sure of which statement will need to be executed at run time, then OCCI provides the execute()
method. Invoking the execute()
method returns one of the following statuses:
While invoking the execute()
method will return one of these statuses, you can also interrogate the statement by using the status
method.
Statement stmt = conn->createStatement(); Statement::Status status = stmt->status(); // status is UNPREPARED stmt->setSQL("select * from emp"); status = stmt->status(); // status is PREPARED
If a statement object is created with a SQL string, then it is created in a PREPARED
state. For example:
Statement stmt = conn->createStatement("insert into foo(id) values(99)"); Statement::Status status = stmt->status();// status is PREPARED status = stmt->execute(); // status is UPDATE_COUNT_AVAILABLE
When you set another SQL statement on the Statement, the status changes to PREPARED
. For example:
stmt->setSQL("select * from emp"); // status is PREPARED status = stmt->execute(); // status is RESULT_SET_AVAILABLE
This section describes the possible values of Status
related to a statement object:
If you have not used the setSQL()
method to attribute a SQL string to a statement object, then the statement is in an UNPREPARED
state.
Statement stmt = conn->createStatement(); Statement::Status status = stmt->status(); // status is UNPREPARED
If a Statement is created with an SQL string, then it is created in a PREPARED
state. For example:
Statement stmt = conn->createStatement("INSERT INTO demo_tab(id) VALUES(99)"); Statement::Status status = stmt->status(); // status is PREPARED
Setting another SQL statement on the Statement will also change the status to PREPARED
. For example:
status = stmt->execute(); // status is UPDATE_COUNT_AVAILABLE stmt->setSQL("SELECT * FROM demo_tab"); // status is PREPARED
A status of RESULT_SET_AVAILABLE
indicates that a properly formulated query has been executed and the results are accessible through a result set.
When you set a statement object to a query, it is PREPARED
. Once you have executed the query, the statement changes to RESULT_SET_AVAILABLE
. For example:
stmt->setSQL("SELECT * from EMP"); // status is PREPARED status = stmt->execute(); // status is RESULT_SET_AVAILABLE
To access the data in the result set, issue the following statement:
ResultSet *rs = Statement->getResultSet();
When a DDL or DML statement in a PREPARED
state is executed, its state changes to UPDATE_COUNT_AVAILABLE
, as shown in the following code example:
Statement stmt = conn->createStatement("INSERT INTO demo_tab(id) VALUES(99)"); Statemnt::Status status = stmt->status(); // status is PREPARED status = stmt->execute(); // status is UPDATE_COUNT_AVAILABLE
This status refers to the number of rows affected by the execution of the statement. It indicates that:
The statement did not include any input or output streams.
The statement was not a query but either a DDL or DML statement.
You can obtain the number of rows affected by issuing the following statement:
Statement->getUpdateCount();
Note that a DDL statement will result in an update count of zero (0
). Similarly, an update that does not meet any matching conditions will also produce a count of zero (0
). In such a case, you cannot infer the kind of statement that has been executed from the reported status.
If there are any output streams to be written, the execute does not complete until all the stream data is completely provided. In this case, the status changes to NEEDS_STREAM_DATA
to indicate that a stream must be written. After writing the stream, call the status() method to find out if more stream data should be written, or whether the execution has completed.
In cases where your statement includes multiple streamed parameters, use the getCurrentStreamParam() method to discover which parameter needs to be written.
If you are performing an iterative or array execute, the getCurrentStreamIteration() method reveals to which iteration the data is to be written.
Once all the stream data has been processed, the status changes to either RESULT_SET_AVAILABLE
or UPDATE_COUNT_AVAILABLE
.
This status indicates that the application requires some stream data to be read in OUT
or IN/OUT
parameters before the execution can finish. After reading the stream, call the status
method to find out if more stream data should be read, or whether the execution has completed.
In cases in which your statement includes multiple streamed parameters, use the getCurrentStreamParam()
method to discover which parameter needs to be read.
If you are performing an iterative or array execute, then the getCurrentStreamIteration()
method reveals from which iteration the data is to be read.
Once all the stream data has been handled, the status changes to UPDATE_COUNT_REMOVE_AVAILABLE
.
The ResultSet
class also has readable streams and it operates similar to the readable streams of the Statement
class.
All SQL DML statements are executed in the context of a transaction. An application causes the changes made by these statement to become permanent by either committing the transaction, or undoing them by performing a rollback. While the SQL COMMIT
and ROLLBACK
statements can be executed with the executeUpdate()
method, you can also call the Connection::commit()
and Connection::rollback()
methods.
If you want the DML changes that were made to be committed immediately, you can turn on the auto commit mode of the Statement
class by issuing the following statement:
Statement::setAutoCommit(TRUE);
Once auto commit is in effect, each change is automatically made permanent. This is similar to issuing a commit right after each execution.
To return to the default mode, auto commit off, issue the following statement:
Statement::setAutoCommit(FALSE);
The statement caching feature establishes and manages a cache of statements within a session. It improves performance and scalability of application by efficiently using prepared cursors on the server side and eliminating repetitive statement parsing.
Statement caching can be used with connection and session pooling, and also without connection pooling. Please review Example 3-28 and Example 3-29 for typical usage scenarios.
Example 3-28 Statement Caching without Connection Pooling
These steps and accompanying pseudocode implement the statement caching feature without use of connection pools:
Create a Connection
by making a createConnection() call on the Environment
object.
Connection *conn = env->createConnection( username, password, connecstr);
Enable statement caching on the Connection
object by using a nonzero size
parameter in the setStmtCacheSize() call.
conn->setStmtCacheSize(10);
Subsequent calls to getStmtCacheSize() would determine the size of the cache, while setStmtCacheSize() call changes the size of the statement cache, or disables statement caching if the size
parameter is set to zero.
Create a Statement
by making a createStatement() call on the Connection
object; the Statement
is returned if it is in the cache already, or a new Statement
with a NULL
tag is created for the user.
Statement *stmt = conn->createStatement(sql);
To retrieve a previously cached tagged statement, use the alternate form of the createStatement() method:
Statement *stmt = conn->createStatement(sql, tag);
Use the statement to execute SQL commands and obtain results.
Return the statement to cache.
conn->terminateStatement(stmt, tag);
If you don't want to cache this statement, use the disableCaching() call and an alternate from of terminateStatement():
stmt->disableCaching(); conn->terminateStatement(stmt);
If you need to verify whether a statement has been cached, issue an isCached() call on the Connection
object.
You can choose to tag a statement at release time and then re-use it for another statement with the same tag. The tag will be used to search the cache. An untagged statement, where tag is NULL
, is a special case of a tagged statement. Two statements are considered different if they only differ in their tags, and if only one of them is tagged.
Terminate the connection.
Example 3-29 Statement Caching with Connection Pooling
These steps and accompanying pseudocode implement the statement caching feature with connection pooling:
Create a ConnectionPool
by making a call to the createConnectionPool() of the Environment
object.
ConnectionPool *conPool = env->createConnectionPool( username, password, connecstr, minConn, maxConn, incrConn);
If using a StatelessConnectionPool
, call createStatelessConnectionPool() instead. Subsequent operations are the same for ConnectionPool
and StatelessConnectionPool
objects.
Stateless ConnectionPool *conPool = env->createStatelessConnectionPool( username, password, connecstr, minConn, maxConn, incrConn, mode);
Enable statement caching for all Connection
s in the ConnectionPool
by using a nonzero size
parameter in the setStmtCacheSize() call.
conPool->setStmtCacheSize(10);
Subsequent calls to getStmtCacheSize() would determine the size of the cache, while setStmtCacheSize() call changes the size of the statement cache, or disables statement caching if the size
parameter is set to zero.
Get a Connection
from the pool by making a createConnection() call on the ConnectionPool
object; the Statement
is returned if it is in the cache already, or a new Statement
with a NULL
tag is created for the user.
Connection *conn = conPool->createConnection(username, password, connecstr);
To retrieve a previously cached tagged statement, use the alternate form of the createStatement() method:
Statement *stmt = conn->createStatement(sql, tag);
Create a Statement
by making a createStatement() call on the Connection
object; the Statement
is returned if it is in the cache already, or a new Statement
with a NULL
tag is created for the user.
Statement *stmt = conn->createStatement(sql);
To retrieve a previously cached tagged statement, use the alternate form of the createStatement() method:
Statement *stmt = conn->createStatement(sql, tag);
Use the statement to execute SQL commands and obtain results.
Return the statement to cache.
conn->terminateStatement(stmt, tag);
If you don't want to cache this statement, use the disableCaching() call and an alternate from of terminateStatement():
stmt->disableCaching(); conn->terminateStatement(stmt);
If you need to verify whether a statement has been cached, issue an isCached() call on the Connection
object.
Release the connection terminateConnection().
conPool->terminateConnection(conn);
Note:
|
Each OCCI method is capable of generating an exception if it is not successful. This exception is of type SQLException
. OCCI uses the C++ Standard Template Library (STL), so any exception that can be thrown by the STL can also be thrown by OCCI methods.
The STL exceptions are derived from the standard exception class. The exception::what()
method returns a pointer to the error text. The error text is guaranteed to be valid during the catch block
The SQLException
class contains Oracle specific error numbers and messages. It is derived from the standard exception class, so it too can obtain the error text by using the exception::what()
method.
In addition, the SQLException
class has two methods it can use to obtain error information. The getErrorCode()
method returns the Oracle error number. The same error text returned by exception::what()
can be obtained by the getMessage()
method. The getMessage()
method returns an STL string so that it can be copied like any other STL string.
Based on your error handling strategy, you may choose to handle OCCI exceptions differently from standard exceptions, or you may choose not to distinguish between the two.
If you decide that it is not important to distinguish between OCCI exceptions and standard exceptions, your catch block might look similar to the following:
catch (exception &excp) { cerr << excp.what() << endl; }
Should you decide to handle OCCI exceptions differently than standard exceptions, your catch block might look like the following:
catch (SQLException &sqlExcp) { cerr <<sqlExcp.getErrorCode << ": " << sqlExcp.getErrorMessage() << endl; } catch (exception &excp) { cerr << excp.what() << endl; }
In the preceding catch block, SQL exceptions are caught by the first block and non-SQL exceptions are caught by the second block. If the order of these two blocks were to be reversed, SQL exceptions would never be caught. Since SQLException
is derived from the standard exception, the standard exception catch block would handle the SQL exception as well.
See Also:
Description of a special feature for handling errors that arise during batch updates, described in section "Modifying Rows Iteratively" in Chapter 12, "Optimizing Performance of OCCI Applications"
Oracle Database Error Messages for more information about Oracle error messages.
In general, OCCI does not cause an exception when the data value retrieved by using the get
xxx
()
methods of the ResultSet
class or Statement
class is NULL
or truncated. However, this behavior can be changed by calling the setErrorOnNull()
method or setErrorOnTruncate()
method. If the setError
xxx
()
methods are called with causeException=TRUE
, then an SQLException
is raised when a data value is NULL
or truncated.
The default behavior is not to raise an SQLException
. A column or parameter value can also be NULL
, as determined by a call to isNull()
for a ResultSet
or Statement
object returning TRUE
:
rs->isNull(columnIndex); stmt->isNull(paramIndex);
If the column or parameter value is truncated, it will also return TRUE as determined by a isTruncated()
call on a ResultSet
or Statement
object:
rs->isTruncated(columnIndex); stmt->isTruncated(paramIndex);
For data retrieved through the setDataBuffer()
method and setDataBufferArray()
method, exception handling behavior is controlled by the presence or absence of indicator variables and return code variables as shown in Table 3-1, Table 3-2, and Table 3-3.
Table 3-1 Normal Data - Not Null and Not Truncated
Return Code | Indicator - not provided | Indicator - provided |
---|---|---|
Not provided |
error = 0 |
error = 0 indicator = 0 |
Provided |
error = 0 return code = 0 |
error = 0 indicator = 0 return code = 0 |
Table 3-2 Null Data
Return Code | Indicator - not provided | Indicator - provided |
---|---|---|
Not provided |
SQLException error = 1405 |
error = 0 indicator = -1 |
Provided |
SQLException error = 1405 return code = 1405 |
error = 0 indicator = -1 return code = 1405 |
Table 3-3 Truncated Data
Return Code | Indicator - not provided | Indicator - provided |
---|---|---|
Not provided |
SQLException error = 1406 |
SQLException error = 1406 indicator = data_len |
Provided |
error = 24345 return code = 1405 |
error = 24345 indicator = data_len return code = 1406 |
In Table 3-3, data_len
is the actual length of the data that has been truncated if this length is less than or equal to SB2MAXVAL
. Otherwise, the indicator is set to -2
.