VisiTransact Guide : Data access using the Session Manager

Data access using the Session Manager
This chapter explains how to use the Session Manager to manage connections between transactional objects and databases in a distributed environment—this includes DirectConnect and XA access. It assumes that you are familiar with the CORBA Transaction Service specification, and database concepts.
Note
Session Manager is supported on the Solaris platform only.
The Session Manager includes the following interfaces:
Connection—represents a transactional database connection.
ConnectionPool—allocates a connection from the pool.
For an overview about the Session Manager and the XA Resource Director, see “Session Manager overview”.
Preparing for integration
Before you can use the features in the Session Manager, you must do the following:
Verify that your application uses the Session Manager (ConnectionPool and Connection interfaces) to obtain connection handles. To obtain connections, use connection profiles by name—the name that was given to the connection profile via the VisiBroker Console.
Using the Session Manager: Summary of steps
The following steps summarize how to work with the Session Manager.
1
2
3
4
5
6
7
Note
You can execute lots of pieces of work for a single transaction. Because connections are pooled, you should keep a Connection object for a short while and not hold onto it. You can get Connection object as often as needed within a single transaction.
The following sections detail each step.
Obtaining a ConnectionPool object reference
The following steps describe the general process for obtaining a reference to the ConnectionPool object, and are followed by a code example.
1
Call the ORB resolve_initial_references() method, passing the object type VISSessionManager::ConnectionPool.
2
Narrow the returned object to a VISSessionManager::ConnectionPool. The following is an example of obtaining a ConnectionPool object reference in C++.
{
CORBA::ORB_var orb = CORBA::ORB_init();
CORBA::Object_var initRef =
orb->resolve_initial_references("VISSessionManager::ConnectionPool");
VISSessionManager::ConnectionPool_var pool =
VISSessionManager::ConnectionPool::_narrow(initRef);
...
}
Using ConnectionPool object references
The ConnectionPool object reference is valid for the entire process under which you create it; you can use it in any thread. You can either make multiple calls to obtain references to the ConnectionPool object or use just one reference throughout the entire process, saving duplicate resolve_initial_references() calls.
Obtaining a Connection object from the Connection Pool
Once the application has obtained a reference to the ConnectionPool object, the getConnection() call can be used to obtain a Connection object which represents this database connection to the application. It is at this point that the Session Manager binds a database connection with a Connection object.
The getConnection() call requires an active implicit transaction context. The getConnectionWithCoordinator()call can be used to explicitly specify a transaction using its Coordinator. For more information about getConnectionWithCoordinator(), see “Using explicit transaction contexts”.
The getConnection() method does the following:
1
If there is a free connection in the pool with the same connection profile, the pool returns that connection. If there is no free connection with a matching connection profile available, the Session Manager creates a new connection. The connection is created using an appropriate method for the specific Session Manager implementation.
Note
You cannot override connection attributes programmatically.
2
3
The following code sample shows how to get a Connection object to represent a connection.
...
VISSessionManager::ConnectionPool_var pool;
// Ask the pool for a database connection
VISSessionManager::Connection_var conn = pool->getConnection("quickstart");
...
Any errors that happen during any of the steps will be returned as exceptions to getConnection() or getConnectionWithCoordinator(). If any of these steps fail, the Session Manager will throw an exception rather than returning a Connection object.
Using explicit transaction contexts
You can get a connection for an explicit transaction context by using getConnectionWithCoordinator(). The getConnectionWithCoordinator() method is used for the following reasons:
If you call getConnectionWithCoordinator() and pass in a Coordinator reference, the Session Manager will use the Coordinator to perform all the tasks it normally would do with the implicit context. Instead of using an implicit VisiTransact-managed transaction, the Session Manager uses the explicitly-stated transaction Coordinator. The connection will be set with this transaction Coordinator until you release the connection.
The following code sample shows getConnectionWithCoordinator() passing transaction context via the Coordinator.
...
VISSessionManager::ConnectionPool_var pool;
// Ask the pool for a database connection using the "quickstart" profile
VISSessionManager::Connection_var conn =
pool->getConnectionWithCoordinator("quickstart", coordinator);
...
For more information about explicit propagation of transaction context, see “Other methods of creating and propagating transactions”.
Optimizing connection pooling
The Session Manager automatically keeps a pool of connections and returns connections to applications based on a set of attributes. For efficient connection pooling, the application should use the same connection profile and attributes for all connections to a single data source.
Getting a native connection handle
To use a connection represented by a Connection object, the application must obtain a native connection handle from the Connection object using the getNativeConnectionHandle() method. Subsequently, the application code talks to the database directly through this native connection handle. This native connection handle can then be used to do normal data access. In other words, you can do the work in the database API with which you are familiar.
The following code sample shows how to get a connection handle to an Oracle database for use with the OCI interface.
...
VISSessionManager::Connection_var conn;
// get an Oracle OCI connection handle
handles = (SampleOraHandle *) smconn->getNativeConnectionHandle();
...
Using the native connection handle
You can use the connection handle obtained through the Session Manager the way you would use any native connection handle: using the native database API. However, some actions are disallowed when using the Session Manager. For all implementations, all calls to the database which have an effect on the transactional state are prohibited, including any calls which begin, commit, or roll back a transaction. Such calls affect the transactional integrity of the work. Transactional effects may be hidden. For example, DDL statements (like create table) in Oracle force an implicit commit call. For more information about actions that are prohibited, see “XA Session Manager for Oracle OCI, version 9i Client” and “DirectConnect Session Manager for Oracle OCI, version 9i Client”.
Threading requirements
The Session Manager automatically manages the database connection's thread requirements, if there are any. The isSupported() method may be used with the thread_portable argument to determine whether connections may be used on other threads. In general, the connection returned by the getConnection() method is only valid on the thread used to acquire it; the application may not use the connection handle on any other thread. There may be relaxed restrictions for some implementations. For more information about thread requirements for a specific database, see “XA Session Manager for Oracle OCI, version 9i Client” and “DirectConnect Session Manager for Oracle OCI, version 9i Client”.
Releasing a connection
Calling release() on a Connection object ends the association of the transaction with a particular connection. Releasing the Connection object does not close the underlying database connection; the connection is returned to the pool for re-use. After releasing a Connection object, the application may not use the native connection handle or Connection object again. If you decide to perform more work on this transaction, you can obtain another Connection object.
There are two methods available for releasing a connection: release() and releaseAndDisconnect(). For usage, see the following table.
Generally, the native connection handle is a pointer. Therefore, you should set to null any copy you have of that pointer when releasing the connection.
If you are implementing an IDL interface call, you must release() or hold() the connection before returning from that call. Failure to do so will circumvent the Session Manager's ability to avoid lost connections due to unreliable clients, and to enforce threading restrictions imposed by some database implementations.
The application must not attempt to disconnect using the database API. It should use release() or releaseAndDisconnect().
Caution
If the application uses a connection handle after a release method is called, unpredictable results will occur.
The following table explains the behavior of the release() and releaseAndDisconnect() methods.
When the application invokes release(), it must use one of the two enumerated values. If the application uses release() with MarkSuccess, the connection is disassociated from the transaction successfully and the connection returns to the pool.
The application uses release() with MarkForRollback to mark the transaction for rollback only. The Session Manager signals to the database and the VisiTransact Transaction Service that the transaction will be rolled back. The application would take such an action when it detects a condition which would invalidate the integrity of the transaction.
The following code sample shows an example of code that releases a transaction successfully.
...
VISSessionManager::Connection_var conn;
conn->release(VISSessionManager::Connection::MarkSuccess);
...
De-allocating the instance of Connection
The release() call does not release the Connection object in the sense of the CORBA _release method. It indicates to the ConnectionPool that the connection will no longer be needed by the application. The application will still need to de-allocate the Connection object. Because these are CORBA objects, you cannot call delete() on them. To ensure the safest management of the Connection object, hold it in a Connection_var. When the Connection_var is destructed, everything gets cleaned up. For the ConnectionPool object, hold it in a ConnectionPool_var.
Note
If release() is not called on a Connection object, the default destructor releases the connection and marks the transaction RollbackOnly. This is an easy way to make sure that all abnormal exits from this method keep the transaction from proceeding. If the application can maintain transactional integrity without rolling back, its own exception handling should release the connection explicitly, marking it for success where appropriate.
Viewing exceptions
Session Manager objects may throw exceptions. The exceptions are defined in IDL. Therefore, the exceptions are handled in the standard CORBA way—the ORB is responsible for transmitting the information back to the caller.
This exception is defined in the VISSessionManager module and contains a sequence of ErrorInfo structures. An ErrorInfo is a struct of reason, subsystem, and error code.
This exception is defined in the ConnectionPool interface and consists of a reason and a code.
If your application receives the ProfileError exception, there are two fields in the exception: a reason and an error code. You can look at these fields to see information about the error.
If your application receives the Error exception, you need to see how long the sequence of ErrorInfos is by invoking the exception.info.length() method on the ErrorInfos sequence. Once you know the length, you can step through each ErrorInfo in the sequence.
The following code sample shows an example of code that catches exceptions in connection calls.
...
try
{
CORBA::ORB_var orb = CORBA::ORB_init();
CORBA::Object_var object =
orb->resolve_initial_references("VISSessionManager::ConnectionPool");
pool = VISSessionManager::ConnectionPool::_narrow(object);

conn = pool->getConnection("quickstart");

lda_ptr = (Lda_Def*) conn->getNativeConnectionHandle();
}
catch (VISSessionManager::ConnectionPool::ProfileError &ex)
{
cerr << "Profile error: " << ex.code << ex.reason << endl;

conn->releaseAndDisconnect();

throw ApplicationException(); //This error is defined by the application
}
catch (VISSessionManager::Connection::Error &ex)
{
cerr << "Session Manager error: " << endl;

int len = ex.info.length();
for (CORBA::ULong i=0; i<len; i++)
{
cerr << ex.info[i].subsystem << "-" << ex.info[i].code
<< ": " << ex.info[i].reason << endl;
}
conn->releaseAndDisconnect();

throw ApplicationException(); //This error is defined by the application
}
...
Viewing attributes
There are two methods you can use to view connection profile attributes. They are used for different purposes: one is used when a connection is currently allocated, the other is used when there is no connection allocated.
For more information about getAttribute() and getProfileAttributes(), and for a list of the connection attributes common to all supported databases, see VISSessionManager module in the VisiBroker for C++ API Reference.
Obtaining Session Manager information
To obtain information such as the version of the Session Manager, whether the hold() method is supported or not, or the database's threading policy, use the following methods:
string* getInfo(in string info_type)
boolean isSupported(in string support_type)
The following information types are common to all the Session Managers:
"version"—Returns the version number of the generic Session Manager. The version number is returned in a 5-field string which is standard in the VisiBroker utility vbver. This info_type does not return specific information about which component you are talking to. This information is to be used for informational purposes.
"version_rm"—Returns the version number of the Resource Manager-specific component of the Session Manager. This information is to be used for informational purposes.
The following support types are available for all types of Session Managers:
"hold"—Returns true if the hold() method is supported; otherwise, returns false.
"thread_portable"—Returns true if the connections are restricted to the thread that made the connection; otherwise, returns false.
The following code sample is an example for using getInfo().
...
VISSessionManager::Connection_var conn;
CORBA::String_var info = conn->getInfo("version");
...
Using hold() and resume()
These methods are used to maintain ownership of a Session Manager Connection when the thread of control returns to a client.
This method is used after a hold() to indicate to the Session Manager that the thread of control for this Connection is now back in process.
Using hold()
The Session Manager requires that it be notified if no thread in the current process is active with respect to this connection. The main reason for this restriction is that if the requester fails or is otherwise unable to return to this process to release its Resources, the Session Manager must be able to clean up any resources used for this connection. If the Session Manager does not have knowledge of whether or not the application is still actively using the connection, it cannot dissociate the transaction and proceed with cleanup.
There is another more subtle reason to use hold(). Some database connections are restricted in their use to a particular thread. Here is an example of what may happen:
Using hold() gives the Session Manager a chance to inform the application that making a second interface call to the same server may not be supported for some Session Manager implementations which do not allow the connection to be used on another thread.
Note
Using hold() monopolizes the connection and affects performance; use hold() only when it is necessary.
The timeout parameter specifies the time in seconds that the Session Manager should wait before timing out the connection and cleaning up its resources. As part of the cleanup process, the connection is returned to the ConnectionPool and the transaction is marked for rollback.
Your application can send multiple hold() requests with no intervening resume() calls. If hold() is called twice, the timer is reset with the new value at each call. For example, if you send hold(60) at 8:42:30, it would expire at 8:43:30. However, if you subsequently invoke hold(45) at 8:42:50, the timer would expire at 8:43:35 because it had been reset by the second hold() call.
Note
Some database Session Manager implementations may not support this method. Your application can use isSupported() to query whether the Session Manager supports the hold() method or not. You can find more information about this in “XA Session Manager for Oracle OCI, version 9i Client” and “DirectConnect Session Manager for Oracle OCI, version 9i Client”
Before the Connection object or the corresponding database connection handle can be used again, resume() must be called on the Connection object.
Using resume()
The resume() cancels the timer associated with the hold() and guarantees that the Session Manager will not modify the underlying connection in any way that would cause conflicts with an active application. Calling resume() when the Connection has not been placed in the hold state results in a VISSessionManager::Error exception, but does not modify the transaction or connection state.
Note
Between the hold() and resume() calls, the application is not allowed to make any other calls on the Connection object or its associated native database handle. If the hold() call timer expires in this interval, the Session Manager has the right to release the connection and mark the transaction for rollback. This is to ensure that Resources held in the application server by that transaction are not left forever if a client dies or never calls again.
Example of a simple integration
The following code sample shows an example program that integrates a DBMS using the Session Manager.
...
void applicationWork(CosTransactions::Coordinator *coordinator)
{
VISSessionManager::ConnectionPool_var pool;
//get the ConnectionPool reference
try
{
CORBA::ORB_var orb = CORBA::ORB_init();
CORBA::Object_var initRef =
orb->resolve_initial_references("VISSessionManager::ConnectionPool");
pool = VISSessionManager::ConnectionPool::_narrow(initRef);
}
catch (CORBA::Exception &ex)
{
cout << "Corba exception obtaining reference to ConnectionPool"
<< endl;
cout << ex << endl;
throw ApplicationException();
}
//Declare the Connection_var on the stack to ensure that it destructs.
VISSessionManager::Connection_var conn;
Lda_Def *lda_ptr = 0;
try
{
// Ask the pool for a database connection
// Use the database profile "quickstart"
conn = pool->getConnection("quickstart");

// get a connection handle to use for native Oracle OCI calls
lda_ptr = (Lda_Def*) conn->getNativeConnectionHandle();
}
catch(const VISSessionManager::ConnectionPool::ProfileError& ex)
{
// we received an error with this profile.
cerr << "Profile error:\n";
<< " " << ex.code
<< ": " << ex.reason
<< endl;
throw ApplicationException();
// This would be something an application would define.
}
}
catch(const VISSessionManager::Error& ex)
{
cerr << "Session Manager error:\n";
// print out all the error messages
for(CORBA::ULong i = 0; i < ex.info.length(); i++)
{
cerr << " " << ex.info[i].subsystem
<< "-" << ex.info[i].code
<< ": " << ex.info[i].reason
<< endl;
}

throw ApplicationException();
// This would be something an application would define.
}

//use lda to access Oracle data.
...

// If they got here, no unhandled exceptions occurred.
// Release the connection successfully
conn->release(VISSessionManager::Connection::MarkSuccess);
}
XA implementation issues
The XA implementation supports full participation in VisiTransact transactions. When using the XA implementation of the Session Manager, some tasks are different than when you are using the DirectConnect implementation. This section provides information about XA-implementation issues.
Completing or recovering a transaction
The Session Manager automatically registers the XA Resource Director with the VisiTransact Transaction Service during the getConnection() call. The Resource Director is ready and waiting for transaction completion (commit or rollback). Once all of the work of the transaction is done and the application invokes commit() or rollback(), the VisiTransact Transaction Service calls the Resource Director to either commit or rollback the transaction. Depending on the circumstances, the Resource Director may coordinate recovery. The Resource Director handles all recovery between XA Resources (the databases) and the VisiTransact Transaction Service without administrator intervention.
For more information about transaction completion and two-phase commit, see “Transaction completion” and “Coordinating transaction completion with Resource objects” For more information about the Resource Director, see “Session Manager overview”.
DirectConnect implementation issues
When using the DirectConnect implementation of the Session Manager, some tasks are different than when you are using the XA implementation.
In DirectConnect transactions, the connection has one of the following type of states:
These last two states provide behavior that serializes access for clients. Access needs to be serialized because two different threads should not be able to use the same connection at the same time. Therefore, two different threads cannot do work on one of these DirectConnect connections at the same time for the same transaction.
Since the transaction state or Resource state is being maintained in a single process, if any element fails, the transaction is rolled back. If the failure occurs during the commit phase, you may not be able to tell if the transaction was committed or not. The commit() may receive either CosTransactions::HeuristicHazard or CORBA::TRANSACTION_ROLLEDBACK, depending on whether the VisiTransact Transaction Service knows what happened.
Completing or recovering a transaction
For DirectConnect transactions, the commit process is a single phase commit. The Resource that is involved is a single phase Resource embedded in the Session Manager. The connection that has been doing the work up until the time to commit will be available in that process (as long as the process stays up) to commit the transaction's work. Once the VisiTransact Transaction Service has been told to commit, it will tell the Resource to perform a single-phase commit, Once the commit happens, the connection is freed and returns to the pool to do work for a different transaction.
If the application server containing the DirectConnect Session Manager goes down, the single phase Resource is forgotten and the transaction is rolled back.
Note
The application must have already invoked release() or releaseAndDisconnect() before the commit so that the connection can be freed up.
Changing from DirectConnect to XA
If you originally develop your application for a DirectConnect environment and then want to use it in an XA environment, there should be no code changes necessary. There is just one basic rule that you should follow: Conform to the programming restrictions for both DirectConnect and XA. The only change necessary to convert from DirectConnect to XA is to use a connection profile configured for the XA implementation of the Session Manager. You must then deploy an XA Resource Director if one is not already deployed for that database.