Oracle9i CORBA Developer's Guide and Reference Release 1 (9.0.1) Part Number A90187-01 |
|
This chapter covers transaction management for CORBA applications. The CORBA developer can choose to use one of the following transactional APIs provided:
In Oracle9i, Java Transaction API (JTA) 1.0.1 for managing transactions. This chapter assumes that you have a working knowledge of JTA. The discussion focuses mostly on examples and explaining the differences between the Sun Microsystems JTA specification and the Oracle JTA implementation. See http://www.javasoft.com
for the Sun Microsystems JTA specification.
Transactions manage changes to multiple databases within a single application as a unit of work. That is, if you have an application that manages data within one or more databases, you can ensure that all changes in all databases are committed at the same time if they are managed within a transaction.
Transactions are described in terms of ACID properties, which are as follows:
The JTA implementation, specified by Sun Microsystems, relies heavily on the JDBC 2.0 specification and XA architecture. The result is a complex requirement on applications in order to ensure that the transaction is managed completely across all databases. Sun Microsystems's specifies Java Transaction API (JTA) 1.0.1 and JDBC 2.0 on http://www.javasoft.com
.
You should be aware of the following when using JTA within the Oracle9i environment:
Whenever your application connected to a database using JDBC or a SQL server, you were creating a transaction. However, the transaction involved only the single database and all updates made to the database were committed at the end of these changes. This is referred to as a local transaction.
A global transaction involves a complicated set of management objects--objects that track all of the objects and databases involved in the transaction. These global transaction objects--TransactionManager
and Transaction
--track all objects and resources involved in the global transaction. At the end of the transaction, the TransactionManager
and Transaction
objects ensure that all database changes are atomically committed at the same time.
Within a global transaction, you cannot execute a local transaction. If you try, the following error will be thrown:
ORA-2089
"COMMIT is not allowed in a subordinate session
."
Some SQL commands implicitly execute a local transaction. All SQL DDL statements, such as "CREATE TABLE", implicitly starts and commits a local transaction under the covers. If you are involved in a global transaction that has enlisted the database that the DDL statement is executing against, the global transaction will fail.
A transaction is said to be demarcated, which means that each transaction has a definite start and stop point. For example, in a client-side demarcated transaction, the client starts the transaction with a begin
method and completes the transaction with either executing the commit
or rollback
method.
The originating client or object that starts the transaction must also end the transaction with a commit or rollback. If the client begins the transaction, calls out to a server object, the client must end the transaction after the invoked method returns. The invoked server object cannot end the transaction.
In a distributed object application, transactions are demarcated differently if the originator is the client or the server. Where the transaction originates defines the transaction as client-side demarcated or server-side demarcated.
The following are the methods that you can use for transaction demarcation. These methods are defined within the javax.transaction.UserTransaction
interface:
public abstract void begin() throws NotSupported, SystemException;
public abstract void commit() throws RollbackException, HeuristicMixedException, HeuristicRollbackException, SecurityException, IllegalStateException, SystemException;
Completes the existing transaction by saving all changes to resources involved in the transaction. The thread is disassociated from this transaction when this method finishes.
Exceptions:
RollbackException
: Thrown if any resource within the transaction could not commit successfully. All resource changes are rolled back.
HeuristicMixedException
: Thrown to indicate that some of the resources were committed; some were rolled back.
HeuristicRollbackException
: Thrown to indicate that some updates to resources involved in the transaction were rolled back.
SecurityException
: Thrown when the thread is not allowed to commit the transaction based on a security violation.
IllegalStateException
: Thrown if the current thread has not been associated with a transaction. This occurs if you try to commit a transaction that was never started.
SystemException
: Thrown if an unexpected error condition occurs.
public abstract void rollback() throws IllegalStateException, SecurityException, SystemException;
Roll back the transaction associated with the current thread.
Exceptions:
SecurityException
: Thrown when the thread is not allowed to roll back the transaction based on a security violation.
IllegalStateException
: Thrown if the current thread has not been associated with a transaction. This occurs if you try to roll back a transaction that was never started.
SystemException
: Thrown if an unexpected error condition occurs.
public abstract int getStatus() throws SystemException;
public abstract void setRollbackOnly() throws IllegalStateException, SystemException;
Modify the transaction associated with the current thread so that the outcome results in a rollback.
Exceptions:
public abstract setTransactionTimeout(int seconds) throws SystemException;
Set the timeout value in seconds for the transaction associated with this current thread. See "Setting the Transaction Timeout" for more information on this method.
Exceptions:
When you begin a transaction within either a client or a server instance, JTA denotes the originator in the transaction manager. As the transaction involves more objects and resources, the transaction manager tracks all of these objects and resources in the transaction and manages the transaction for these entities.
When an object calls another object, in order for the invoked object to be included in the transaction, JTA propagates the transaction context to the invoked object. Propagation of the transaction context is necessary for including the invoked object into the global transaction.
As shown in Figure 7-1, if the client begins a global transaction, calls a server object in the database, the transaction context is propagated to the server object. If this server object invokes another server object, within the same or a remote database, the transaction context is propagated to this object as well. This ensures that all objects that are supposed to be involved in the global transaction are tracked by the transaction manager.
While there are several methods for retrieving a JDBC connection to a database, only one of these methods causes the database to be included in a JTA transaction. The following table lists the normal methods for retrieving JDBC connections:
Of these methods, only the DataSource
object can be used to include a database resource in the global transaction. In order to ensure that the statements are included within a global transaction, you must do the following:
DataSource
object (OracleJTADataSource
) in the JNDI namespace. There are several types of DataSource
objects that you can bind. You must bind the JTA type in order for this database to be included in the global transaction.
DataSource
object from the JNDI namespace after the global transaction has started.
DataSource
object using the getConnection
method.
An example is shown in "Enlisting Resources on the Server-side".
If your transaction involves more than one database, you must specify an Oracle9i database as the two-phase commit engine. See "Configuring Two-Phase Commit Engine" for more information.
One of the primary advantages for a global transaction is the number of objects and database resources managed as a single unit within the transaction. If your global transaction involves more than one database resource, you must specify a two-phase commit engine, which is an Oracle9i database designated to manage the changes to all databases within the transaction. The two-phase commit engine is responsible for ensuring that when the transaction ends, all changes to all databases are either totally committed or fully rolled back.
On the other hand, if your global transaction has multiple server objects, but only a single database resource, you do not need to specify a two-phase commit engine. The two-phase commit engine is required only to synchronize the changes for multiple databases. If you have only a single database, single-phase commit can be performed by the transaction manager.
Note: Your two-phase commit engine can be any Oracle9i database. It can be the database where your server object exists, or even a database that is not involved in the transaction at all. See "Configuring Two-Phase Commit Engine" for a full explanation of the two-phase commit engine setup. |
Figure 7-2 shows three databases enlisted in a global transaction and another database that is designated as the two-phase commit engine. When the global transaction ends, the two-phase commit engine ensures that all changes made to the databases A, B, and the local are committed or rolled back simultaneously.
The following sections summarize the details for demarcating the transaction and enlisting the database in the transaction. These details are explained and demonstrated in the rest of the chapter. However, these tables provide a reference point for you.
Before you can retrieve the UserTransaction
or DataSource
bound objects from the JNDI namespace, you must provide the following before the JNDI lookup:
Table 7-2 Environment Setup For Transactional Object Retrieval
The DataSource
object is used to explicitly enlist the database in the JTA transaction. In order for the database to be correctly enlisted, the DataSource
must be bound correctly, and the retrieval mechanism can be one of three methods. These are discussed below:
Table 7-4 summarizes the single-phase commit scenario. It covers the JNDI binding requirements and the application implementation runtime requirements.
Table 7-5 summarizes the two-phase commit scenario.
To retrieve any objects or database resources, you can perform in-session activation or remote lookup.
UserTransaction
is always local, and DataSource
objects can be local or remote. For local retrieval of any of these objects, you can activate these objects within this session. T
he namespace is always local, so you do not have to provide authentication information, namespace URL, or the "jdbc_access://
" prefix. In this scenario, the lookup
would require only the JNDI name, In addition, the initial context can be created without any set environment.
DataSource
object is remote, so you must still provide all of the same information that was provided in the client scenario: authentication information, namespace URL, and the "jdbc_access://
" prefix. For remote retrieval, perform exactly as demonstrated in the "JTA Client-Side Demarcation".
The following example demonstrates a server object performing an in-session lookup of the UserTransaction
and DataSource
objects. This example uses a single phase commit transaction. Notice that because this is an in-session activation, none of the following are needed: authentication information, location of the namespace, and the "jdbc_access://
" prefix.
ic = new InitialContext ( ); // lookup the usertransaction UserTransaction ut = (UserTransaction)ic.lookup ("/test/myUT"); ... ut.begin ();// Retrieve the DataSource
DataSource ds = (DataSource)ic.lookup ("/test/empDB");
// Get connection to the database through DataSource.getConnection
Connection conn = ds.getConnection ();
For JTA, client-side demarcated transactions are programmatically demarcated through the UserTransaction
interface (see "UserTransaction Interface"). A UserTransaction
object must be bound with the bindut
command into the namespace (see "Bind UserTransaction Object in the Namespace"). With client-side transaction demarcation, the client controls the transaction. The client starts a global transaction by invoking the UserTransaction
begin
method; it ends the transaction by invoking either the commit
or rollback
methods. In addition, the client must always set up an environment including a Hashtable
with authentication information and namespace location URL.
Figure 7-3 shows a client invoking a server object. The client starts a global transaction, then invokes the object. The transactional context is propagated to include the server object.
The following must occur for the client to demarcate the transaction:
Hashtable
environment with the namespace address and authentication information.
UserTransaction
object from the namespace within the client logic. When you retrieve the UserTransaction
object from any client, the URL must consist of "jdbc_access://
" prefix before the JNDI name.
UserTransaction.begin()
.
UserTransaction.commit()
or UserTransaction.rollback()
.
Example 7-2 shows a client that invokes a server object within the transaction.
Before starting the client, you must first bind the UserTransaction
object in the namespace. To bind a UserTransaction
object to the name "/test/myUT
" in the namespace located on nsHost
, execute the following:
sess_sh -service jdbc:oracle:thin:@nsHost:5521:ORCL -user SCOTT -password TIGER & bindut /test/myUT
See "Bind UserTransaction Object in the Namespace" for more information.
After binding the UserTransaction
object, your client code can retrieve the UserTransaction
object and start a global transaction. Since the client is retrieving the UserTransaction
object from a remote site, the lookup requires authentication information, location of the namespace, and the "jdbc_access://
" prefix.
EmployeeInfo info; String sessiiopURL = args [0]; String objectName = args [1]; //Set up the service URL to where the UserTransaction object //is bound. Since from the client, the connection to the database //where the namespace is located can be communicated with over either //a Thin or OCI JDBC driver. This example uses a Thin JDBC driver.String namespaceURL = "jdbc:oracle:thin:@nsHost:1521:ORCL";
// lookup usertransaction object in the namespace//1.(a) Authenticate to the database.
// create InitialContext and initialize for authenticating client Hashtable env = new Hashtable (); env.put (Context.URL_PKG_PREFIXES, "oracle.aurora.jndi"); env.put (Context.SECURITY_PRINCIPAL, "SCOTT"); env.put (Context.SECURITY_CREDENTIALS, "TIGER"); env.put (Context.SECURITY_AUTHENTICATION, ServiceCtx.NON_SSL_LOGIN);//1.(b) Specify the location of the namespace where the transaction objects
// are bound.
env.put(jdbc_accessURLContextFactory.CONNECTION_URL_PROP, namespaceURL);
Context ic = new InitialContext (env);//2. Retrieve the UserTransaction object from JNDI namespace
UserTransactionut = (UserTransaction)ic.lookup ("jdbc_access://test/myUT");
//3. Start the transaction
ut.begin();
//4. Retrieve the server object reference
// lookup employee object in the namespace Employee employee = (Employee)ic.lookup
("sess_iiop://myhost:1521:orcl/test/employee");//5. Perform business logic.
...//6. End the transaction
//Commit the updated value ut.commit ();
The previous example showed how a transaction context was propagated to server objects from a client within the JTA global transaction. When you execute the server object, the transaction is propagated over the IIOP transport layer. In addition to invoking IIOP server objects, you may wish to update databases over JDBC connections. This section shows how you enlist databases using a JDBC connection in tandem with the IIOP server object propagation.
Figure 7-4 demonstrates how the client can open both an IIOP and a JDBC connection to the database. To open the JDBC connection within the context of a global transaction, you must use a JTA DataSource
object.
To include a remote database within the transaction from a client, you must use a DataSource
object, which has been bound in the namespace as a JTA DataSource
. Then, invoke the getConnection
method of the DataSource
object after the transaction has started, and the database is included in the global transaction. See "Enlisting Resources" for more information.
The following must occur in the client runtime to demarcate the transaction:
Hashtable
environment with the namespace address and authentication information.
UserTransaction
object from the namespace within the client logic. When you retrieve the UserTransaction
object from the client, the URL must consist of "jdbc_access://
" prefix before the JNDI name.
UserTransaction.begin()
.
UserTransaction.commit()
or UserTransaction.rollback()
.
Example 7-3 shows a client that invokes a server object and enlists a single database within the transaction.
Before starting the client, you must first bind the UserTransaction
and DataSource
objects in the JNDI namespace. See "Bind UserTransaction Object in the Namespace" and "Bind DataSource Object in the Namespace" for directions on the binding these objects.
sess_sh -service jdbc:oracle:thin:@nsHost:5521:ORCL -user SCOTT -password TIGER > bindut /test/myUT > bindds /test/DataSource/empDB -url jdbc:oracle:thin:@empHost:5521:ORCL
-dstype jta
The following example follows the steps listed in "JTA Client-Side Demarcation Including Databases".
//Set up the service URL to where the UserTransaction object //is bound. Since from the client, the connection to the database //where the namespace is located can be communicated with over either //a Thin or OCI JDBC driver. This example uses a Thin JDBC driver.String namespaceURL = "jdbc:oracle:thin:@nsHost:1521:ORCL";
// lookup usertransaction object in the namespace//1.(a) Authenticate to the database.
// create InitialContext and initialize for authenticating client Hashtable env = new Hashtable (); env.put (Context.URL_PKG_PREFIXES, "oracle.aurora.jndi"); env.put (Context.SECURITY_PRINCIPAL, "SCOTT"); env.put (Context.SECURITY_CREDENTIALS, "TIGER"); env.put (Context.SECURITY_AUTHENTICATION, ServiceCtx.NON_SSL_LOGIN);//1.(b) Specify the location of the namespace where the transaction objects
// are bound.
env.put(jdbc_accessURLContextFactory.CONNECTION_URL_PROP, namespaceURL);
Context ic = new InitialContext (env);//2. Retrieve the UserTransaction object from JNDI namespace
ut = (UserTransaction)ic.lookup ("jdbc_access://test/myUT");
//3. Start the transaction
ut.begin();
//4.(a) Retrieve the DataSource
(that was previously bound with bindds in // the namespace. After retrieving the DataSource... // get a connection to a database. You need to provide authentication info // for a remote database lookup, similar to what you would do from a client. // In addition, if this was a two-phase commit transaction, you must provide // the username and password.DataSource ds = (DataSource)ic.lookup ("jdbc_access://test/empDB");
//4.(b). Get connection to the database
through DataSource.getConnection // in this case, the database requires the same username and password as // set in the environment.Connection conn = ds.getConnection ("SCOTT", "TIGER");
//5. Retrieve the server object reference
// lookup employee object in the namespace Employee employee = (Employee)ic.lookup (sessiiopURL + objectName);//6. Perform business logic.
...//7. Close the database connection.
conn.close ();//8. End the transaction
//Commit the updated value ut.commit (); }
The databases that the object accesses must be enlisted to be included within the global transaction. This is discussed more in "Enlisting Resources" and "Bind DataSource Object in the Namespace".
If you access an Oracle9i database from the server that should be included in the transaction, you must open the connection to the database after the global transaction starts.
The following example enlists a database in the global transaction.
//retrieve the initial context. InitialContext ic = new InitialContext (); // lookup the usertransaction UserTransaction ut = (UserTransaction)ic.lookup ("/test/myUT"); //start the transaction ut.begin (); // get a connection to the local database. If this was a two-phase commit // transaction, you would provide the username and password for the 2pc engineDataSource ds = (DataSource)ic.lookup (dsName);
// get connection to the local database through DataSource.getConnectionConnection conn = ds.getConnection ("SCOTT", "TIGER");
//perform your SQL against the database. //prepare and execute a sql statement. retrieve the employee's selected benefits PreparedStatement ps = conn.prepareStatement ("update emp set ename = :(employee.name),
sal = :(employee.salary) where empno = :(employee.number)"); .... //do work ps.close(); } //close the connectionconn.close();
// commit the transaction ut.commit (); //return the employee information. return new EmployeeInfo (name, empno, (float)salary);
As in Example 7-4, you would retrieve the JTA DataSource
from the JNDI provider, retrieve the connection, retrieve a context from that connection, and then provide the context on the SQLJ command-line.
//retrieve the initial context. InitialContext ic = new InitialContext (); // lookup the usertransaction UserTransaction ut = (UserTransaction)ic.lookup ("/test/myUT"); //start the transaction ut.begin (); // get a connection to the local database. If this was a two-phase commit // transaction, you would provide the username and password for the 2pc engine DataSource ds = (DataSource)ic.lookup (dsName); // get connection to the local database through DataSource.getConnection Connection conn = ds.getConnection ("SCOTT", "TIGER"); //setup the context for issuing SQLJ against the database DefaultContext defCtx = new DefaultContext (conn); //issue SQL DML statements against the database #sql [defCtx] { update emp set ename = :(remoteEmployee.name), sal = :(remoteEmployee.salary) where empno = :(remoteEmployee.number) }; //close the connection conn.close(); // commit the transaction ut.commit (); //return the employee information. return new EmployeeInfo (name, empno, (float)salary);
For most global transactions, you will need to bind at least one of the following objects in the namespace:
UserTransaction
object
DataSource
object--Necessary for specifying databases that will be included in the transaction.
The bindut
command binds a UserTransaction
object in the namespace. This object is used for demarcation of global transactions by either a client or by an object.
You must bind a UserTransaction
object for both single and two-phase commit transactions through the bindut
command of the sess_sh
tool.
The options used to bind a UserTransaction
object depend on whether the transaction uses a single or two-phase commit, as described below:
Single-phase commit requires the JNDI bound name for the UserTransaction
object. You do not need to provide the address to a two-phase commit engine. For example, the following binds a UserTransaction
with the name of "/test/myUT
" that exists for a single-phase commit transaction:
bindut /test/myUT
To bind a UserTransaction
object to the name "/test/myUT
" in the namespace located on nsHost
through the sess_sh
command, execute the following:
sess_sh -service jdbc:oracle:thin:@nsHost:5521:ORCL -user SCOTT -password TIGER & bindut /test/myUT
Two-phase commit binding requires the JNDI bound name for the UserTransaction
object and the address to a two-phase commit engine. You provide a URL for the two-phase commit engine in the bindut
command, which can be either a JDBC URL or a sess_iiop
URL.
In addition, you can bind a username and password with the UserTransaction
object.
UserTransaction
, the user that retrieved the UserTransaction
will be the same user that is used to perform the commit or rollback for the two-phase commit on all involved databases.
UserTransaction
, then this is the username that the two-phase commit will be committed or rolled back with on all involved databases. The transaction will be started by the user that retrieves the UserTransaction
object; it will be completed by the user bound with the UserTransaction
object.
The username that is used to commit or rollback the two-phase commit transaction must be created on the two-phase commit engine and on each database involved in the transaction. It needs to be created so that it can open a session from the two-phase commit engine to each of the involved databases using database links. Secondly, it must be granted the CONNECT
, RESOURCE
, CREATE
SESSION
privileges to be able to connect to each of these databases. For example, if the user that is needed for completing the transaction is SCOTT, you would do the following on the two-phase commit engine and each database involved in the transaction:
CONNECT SYSTEM/MANAGER;
CREATE USER SCOTT IDENTIFIED BY SCOTT;
GRANT CONNECT, RESOURCE, CREATE SESSION TO SCOTT;
Lastly, if you bound a username and password with the UserTransaction
object, it will be using a different username to finalize the transaction than the username used to start the transaction. For this to be allowed, you must grant the FORCE
ANY
TRANSACTION
privileges on each database involved in the transaction in order for two separate users to start and stop the transaction. If SCOTT
is the username bound with the UserTransaction
object, you would need to do the following in addition to the previous grant:
GRANT FORCE ANY TRANSACTION TO SCOTT;
The following binds a UserTransaction
with the name of "/test/myUT
" and a two-phase commit engine at "2pcHost
" using a JDBC URL:
bindut /test/myUT -url jdbc:oracle:thin:@2pcHost:5521:ORCL
To bind the UserTransaction
in the namespace designating the two-phase commit engine at dbsun.mycompany.com
with a sess_iiop
URL:
bindut /test/myUT -url sess_iiop://dbsun.mycompany.com:2481:ORCL
When the transaction commits, the UserTransaction
communicates with the two-phase engine designated in the -url
option to commit all changes to all included databases. In this example, the username and password were not bound with the UserTransaction
object, so the user that retrieves the UserTransaction
object from the JNDI namespace is used to start and stop the transaction. Thus, this user must exist on all involved databases and the two-phase commit engine. The UserTransaction
tracks all databases involved in the transaction; the two-phase commit engine uses the database links for these databases to complete the transaction.
The bindds
command binds a DataSource
object in the JNDI namespace. In order to enlist any database in a global transaction--including the local database--you must bind a JTA DataSource
object to identify each database included in the transaction. There are multiple types of DataSource
objects for use with certain scenarios. However, for use with JTA transactions, you must bind a JTA DataSource
object, also known as an OracleJTADataSource
object, to identify each database included in the transaction. See the bindds
command of the sess_sh
tool in the Oracle9i Java Tools Reference for a description of other DataSource
object types.
In a single-phase commit scenario, the transaction only includes a single database in the transaction. Since no coordination for updates to multiple databases is needed, you do not need to specify a coordinator. Instead, you simply provide the JNDI bound name and the URL address information for this database within the OracleJTADataSource
object. You do not need to provide a database link for a transaction coordinator.
Use the bindds
command of the sess_sh
tool to bind an DataSource
object in the namespace. The full command is detailed in the Oracle9i Java Tools Reference. For example, the following binds an OracleJTADataSource
with the name of "/test/empDS
" that exists within a single-phase commit transaction with the bindds
command:
bindds /test/empDS -url jdbc:oracle:thin:@empHost:5521:ORCL -dstype jta
After binding the DataSource
object in the namespace, the server can enlist the database within a global transaction.
If multiple databases are to be included in the global transaction, you will need a two-phase commit engine, which is an Oracle9i database that is configured to be the transaction coordinator. Basically, the two-phase commit engine must have database links to each of the databases involved in the transaction. When the transaction ends, the transaction manager notifies the two-phase commit engine to either coordinate the commit of all changes to all involved databases or coordinate a roll back of these same changes.
In order to facilitate this coordination, you must configure the following:
OracleJTADataSource
object.
DataSource
(OracleJTADataSource
) object for each database in the transaction. You must include the following in the bindds command:
The following example binds the empDS
JTA DataSource
into the namespace with 2pcToEmp
as the database link name created on the two-phase commit engine:
% bindds /test/empDS -url jdbc:oracle:thin:@dbsun:5521:ORCL
-dstype jta -dblink 2pcToEmp.oracle.com
When multiple databases are included in a global transaction, the changes to these resources must all be committed or rolled back at the same time. That is, when the transaction ends, the transaction manager contacts a coordinator--also known as a two-phase commit engine--to either commit or roll back all changes to all included databases. The two-phase commit engine is an Oracle9i database that is configured with the following:
In order to facilitate this coordination, you must configure the following:
CREATE
DATABASE
LINK
command) from the two-phase commit engine to each database that may be involved in the global transaction. This is necessary for the two-phase commit engine to communicate with each database at the end of the transaction. These database link names must be included when binding the JTA DataSource
(OracleJTADataSource)
object.
DataSource
(OracleJTADataSource
) object for each database in the transaction. You must include the following in the bindds
command:
Provide the fully-qualified database link name in the -dblink
option of bindds
for each individual database when binding that database's DataSource
into the namespace.
bindds /test/empDS -url jdbc:oracle:thin:@empHost:5521:ORCL
-dstype jta -dblink 2pcToEmp.oracle.com
CONNECT
, RESOURCE
, and CREATE
SESSION
privileges. If the user that completes the transaction is different from the user that starts the transaction, you also need to grant the FORCE
ANY
TRANSACTION
privilege. These privileges must be granted on all databases included in the transaction.
The decision on whether the FORCE
ANY
TRANSACTION
privilege is needed is determined by whether you bound a username and password with the UserTransaction
object.
UserTransaction
, the user that retrieved the UserTransaction
will be the same user that is used to perform the commit or rollback for the two-phase commit on all involved databases.
UserTransaction
, then this is the username that the two-phase commit will be committed or rolled back with on all involved databases. The transaction will be started by the user that retrieves the UserTransaction
object.
Both types of users must be created, so that it can open a session from the two-phase commit engine to each of the involved databases. Secondly, it must be granted the CONNECT
, RESOURCE
, CREATE
SESSION
privileges to be able to connect to each of these databases. For example, if the user that is needed for completing the transaction is SCOTT, you would do the following on the two-phase commit engine and each database involved in the transaction:
CONNECT SYSTEM/MANAGER;
CREATE USER SCOTT IDENTIFIED BY SCOTT;
GRANT CONNECT, RESOURCE, CREATE SESSION TO SCOTT;
Lastly, if you bound a username and password with the UserTransaction
object, it will be using a different username to finalize the transaction than the username used to start the transaction. For this to be allowed, you must grant the FORCE
ANY
TRANSACTION
privileges on each database involved in the transaction in order for two separate users to start and stop the transaction.
The advantage of binding a username with the UserTransaction
is that it is treated as a global user is always committing all transactions started with this UserTransaction
object. Thus, if you have more than one JTA transactions, you will only have to create one user and grant privileges to that user on all involved databases.
For example, if SCOTT
is the username bound with the UserTransaction
object, you would need to do the following in addition to the previous grant:
GRANT FORCE ANY TRANSACTION TO SCOTT;
UserTransaction
into the namespace. You must provide the two-phase commit engine's fully-qualified database address. At this point, you should decide (based on the discussion in step 3) on whether to bind it with a username and password. The following assumes a global username is bound with the UserTransaction
.
bindut /test/myUT -url sess_iiop://dbsun.mycompany.com:2481:ORCL
-user SCOTT -password TIGER
The following example shows a server object that performs an in-session activation to retrieve both the UserTransaction
and DataSource
objects that have been bound locally. The UserTransaction
was bound with the two-phase commit engine's URL, username, and password. The DataSource
objects were all bound with the proper database links.
//with the environment set, create the initial context. InitialContext ic = new InitialContext (); UserTransaction ut = (UserTransaction)ic.lookup ("/test/myUT"); //With the same username and password for the 2pc engine, // lookup the local datasource and a remote database. DataSource localDS = (DataSource)ic.lookup ("/test/localDS"); //remote lookup requires environment setup Hashtable env = new Hashtable (); env.put (Context.URL_PKG_PREFIXES, "oracle.aurora.jndi"); env.put (Context.SECURITY_PRINCIPAL, user); env.put (Context.SECURITY_CREDENTIALS, password); env.put (Context.SECURITY_AUTHENTICATION, ServiceCtx.NON_SSL_LOGIN); env.put(jdbc_accessURLContextFactory.CONNECTION_URL_PROP, namespaceURL); Context ic = new InitialContext (env); //retrieve the DataSource for the remote database DataSource remoteDS = (DataSource)ic.lookup ("jdbc_access://test/NewYorkDS"); //retrieve connections to both local and remote databases Connection localConn = localDS.getConnection (); Connection remoteConn = remoteDS.getConnection (); ... //close the connections localConn.close(); remoteConn.close(); //end the transaction ut.commit();
If you want to bind only a single DataSource
object in the namespace to be used for multiple database resources, you must do the following:
DataSource
without specifying the URL, host, port, SID, or driver type. Thus, you execute the bindds
tool with only the -dstype
jta
option, as follows:
sess_sh -service jdbc:oracle:thin:@nsHost:5521:ORCL -user SCOTT -password TIGER & bindds /test/empDS -dstype jta
DataSource
in your code. When you perform the lookup, you must cast the returned object to OracleJTADataSource
instead of DataSource
. The Oracle-specific version of the DataSource
class contains methods to set the DataSource
properties.
OracleJTADataSource
.getConnection
method as indicated in the other examples.
The following example retrieves a generically bound DataSource
from the namespace using in-session lookup and initializes all relevant fields.
//retrieve an in-session generic DataSource object OracleJTADataSource ds =
(OracleJTADataSource)ic.lookup ("java:comp/env/test/empDS"); //set all relevant properties for my database //URL is for a local database so use the KPRB URL ds.setURL ("jdbc:oracle:kprb:"); //Used in two-phase commit, so provide the fully qualified database link that //was created from the two-phase commit engine to this database ds.setDBLink("localDB.oracle.com"); //Finally, retrieve a connection to the local database using the DataSource Connection conn = ds.getConnection ();
A global transaction automatically has an idle timeout of 60 seconds. If the session attached to the transaction is idle for over the timeout limit, the transaction is rolled back. If any activity occurs within this timeframe, the timeout is reset to zero.
To initialize a different timeout, set the timeout value--in seconds--through the setTransactionTimeout
method before the transaction is begun. If you change the timeout value after the transaction begins, it will not affect the current transaction. The following example sets the timeout to 2 minutes (120 seconds) before the transaction begins.
//create the initial context InitialContext ic = new InitialContext ( ); //retrieve the UserTransaction object ut = (UserTransaction)ic.lookup ("/test/myUT"); //set the timeout value to 2 minutes ut.setTransactionTimeout (120); //begin the transaction ut.begin //Update employee table with new employees updateEmployees(emp, newEmp); //end the transaction. ut.commit ();
The following are the portions of the JTA specification that Oracle9i does not support.
Nested transactions are not supported in this release. If you attempt to begin a new transaction before committing or rolling back any existing transaction, the transaction service throws a NotSupportedException
exception.
The transaction services supplied with this release do not interoperate with other JTA implementations.
The global transaction timeout does not work. In addition, the UserTransaction
idle timeout (setTransactionTimeout
method) starts only when a database connection is closed and idle. Oracle recommends that you do not use timeouts.
With JTS, you demarcate the transaction off of a transaction context, which you can retrieve from the TransactionService
object. The transaction context contains the begin, commit, rollback, suspend, and resume methods. One of the disadvantages to JTS is that you cannot use a two-phase commit engine to coordinate changes to multiple databases. The advantage to JTS is that you can suspend and resume the transaction. Also, because it is specific to CORBA, you can use either Java or non-Java languages in your application.
This implementation of JTS does not manage distributed transactions. Transaction control distributed among multiple database servers, with support for the required two-phase commit protocol, is only available within the JTA implementation.
The JTS transaction API supplied with Oracle9i manages only one resource: an Oracle9i database session. A transaction exists within only a single server, which means that it cannot span multiple servers or multiple database sessions in a single service. Transaction contexts are never propagated outside a server. If a server object calls out to another server, the transaction context is not carried along. However, a transaction can involve one or many objects. The transaction can encompass one or many methods within these objects.
Whether you demarcate the transaction on the client or the server, the following must occur:
TransactionService
object.
Oracle9i automatically initializes this object for any server objects; thus, only the client must explicitly initialize this object. The initialization is accomplished through the AuroraTransactionService.initialize
method.
TransactionService
object through the static TS.getTS
method.
TransactionService.getCurrent
method.
Current
class) methods: begin
, commit
, rollback
, rollback_only
, suspend
, resume
.
The only difference between client and server-side demarcation is that the client must initialize the TransactionService
object before retrieving it. The client initializes a TransactionService
object on the intended server. Since JTS can only manage a transaction within a single server, the client should invoke server objects that exist only on that single server. In addition, any SQL statements executed against the database should also be solely applied to the same server.
The following example demonstrates the steps required for a client-side demarcation:
TransactionService
object. The initialization is accomplished through the AuroraTransactionService.initialize
method.
TransactionService
object through the static TS.getTS
method.
TransactionService.getCurrent
method.
Current
class) methods: begin
, commit
, rollback
, rollback_only
, suspend
, resume
.
import employee.*; import oracle.aurora.jndi.sess_iiop.ServiceCtx; import oracle.aurora.jts.client.AuroraTransactionService; import oracle.aurora.jts.util.*; import javax.naming.Context; import javax.naming.InitialContext; import java.util.Hashtable; public class Client { public static void main (String[] args) throws Exception { if (args.length != 4) { System.out.println ("usage: Client serviceURL objectName user password"); System.exit (1); } String serviceURL = args [0]; String objectName = args [1]; String user = args [2]; String password = args [3]; //The environment must be setup with the correct authentication //and prefix information before you create the initial context Hashtable env = new Hashtable (); env.put (Context.URL_PKG_PREFIXES, "oracle.aurora.jndi"); env.put (Context.SECURITY_PRINCIPAL, user); env.put (Context.SECURITY_CREDENTIALS, password); env.put (Context.SECURITY_AUTHENTICATION, ServiceCtx.NON_SSL_LOGIN); Context ic = new InitialContext (env); //provide the intial context and the service URL of the server AuroraTransactionService.initialize (ic, serviceURL); //Since JTS can only manage transactions on a single server, the //destination server object exists on the same server as the transaction //service. Thus, you use the same service URL to retrieve the object. Employee employee = (Employee)ic.lookup (serviceURL + objectName); EmployeeInfo info; //Use the static method getTS to retrieve the TransactionService and the //static method getCurrent to retrieve the current transaction context. //Off of the Current object, you can start the transaction with the begin //method. All three methods have been combined as follows: TS.getTS ().getCurrent ().begin (); //invoke a method on the retrieved server object. Since the object exists //on the transaction server, it is included in the transaction. info = employee.getEmployee ("SCOTT"); System.out.println (info.name + " " + " " + info.salary); System.out.println ("Increase by 10%"); info.salary += (info.salary * 10) / 100; employee.updateEmployee (info); info = employee.getEmployee ("SCOTT"); System.out.println (info.name + " " + " " + info.salary); //Finally, commit the transaction with the Current.commit method. TS.getTS ().getCurrent ().commit (true); } }
Oracle9i initializes the TransactionService
for any server object. In the same manner as the client, the server must invoke only other server objects on the same server. SQL statements should also only be applied to the same database.
The following example demonstrates the steps required for a client-side demarcation:
TransactionService
object through the static TS.getTS
method.
TransactionService.getCurrent
method.
Current
class) methods: begin
, commit
, rollback
, rollback_only
, suspend
, resume
.
package employeeServer; import employee.*; import java.sql.*; import oracle.aurora.jts.util.*; import org.omg.CosTransactions.*; public class EmployeeImpl extends _EmployeeImplBase { Control txn; public EmployeeInfo getEmployee (String name) throws SQLError { //When the client invokes the getEmployee method, the transaction is started //Retreive the Transaction service through the static getTS method. //Retrieve the current transaction context through the getCurrent method. //And start the transaction with the Current.begin method. These have //been combined into one statement.... TS.getTS ().getCurrent ().begin (); //Retrieve the employee information given the employee name. int empno = 0; double salary = 0.0; #sql { select empno, sal into :empno, :salary from emp where ename = :name }; //At this point, we suspend the transaction to return the employee //information to the client. txn = TS.getTS().getCurrent().suspend(); return new EmployeeInfo (name, empno, (float)salary); } public void updateEmployee (EmployeeInfo employee) throws SQLError { //After the client retrieves the employee info, it invokes the updateEmp //method to change any values. //The transaction is resumed in this method through the Current.resume, //which requires the Control object returned on the suspend method. TS.getTS().getCurrent().resume(txn); //update the employee's information. #sql { update emp set ename = :(employee.name), sal = :(employee.salary) where empno = :(employee.number) }; //Once finished, complete the transaction with the Current.commit method. TS.getTS ().getCurrent ().commit (true); }
The implementations of JTS that is supplied for this Oracle9i release is intended to support client-side transaction demarcation. It has limitations that you should be aware of when designing your application.
This implementation of JTS does not manage distributed transactions. Transaction control distributed among multiple database servers, with support for the required two-phase commit protocol, is only available within the JTA implementation.
The JTS transaction API supplied with Oracle9i manages only one resource: an Oracle9i database session. A transaction cannot span multiple servers or multiple database sessions in a single service.
Transaction contexts are never propagated outside a server. If a server object calls out to another server, the transaction context is not carried along.
However, a transaction can involve one or many objects. The transaction can encompass one or many methods of these objects. The scope of a transaction is defined by a transaction context that is shared by the participating objects. For example, your client can invoke one or more objects on the same server within a single session or several objects on the same server within multiple sessions.
Nested transactions are not supported in this release. If you attempt to begin a new transaction before committing or rolling back any existing transaction, the transaction service throws a SubtransactionsUnavailable
exception.
Methods of the JTS that support transaction timeout, such as setTimeout()
, do not work in this release. You can invoke them from your code, and no exception is thrown, but they have no effect.
The transaction services supplied with this release do not interoperate with other OTS implementations.
Oracle9i supports a version of the JTS. The JTS is a Java mapping of the OMG Object Transaction Service (OTS). There are two classes that the application developer can use:
UserTransaction
, implemented by oracle.aurora.jts.client.AuroraTransactionService
Use the TransactionService
to initialize a transaction context on the client. Include the AuroraTransactionService
package in your Java client source with the following import statements:
import oracle.aurora.jts.client.AuroraTransactionService; import javax.jts.*; import oracle.aurora.jts.util.*;
These classes are included in the library file aurora_client.jar
, which must be in the CLASSPATH when compiling and executing all source files that use the JTS.
There is only one method in this package that you can call:
public synchronized static void initialize(Context initialContext, String serviceName)
This method initializes the transaction context on a client. The parameters are:
serviceName |
The complete service name. For example |
An example of using initialize()
is:
Hashtable env = new Hashtable(); env.put(Context.URL_PKG_PREFIXES, "oracle.aurora.jndi"); env.put(Context.SECURITY_PRINCIPAL, "scott"); env.put(Context.SECURITY_CREDENTIALS, "tiger"); env.put(Context.SECURITY_AUTHENTICATION, ServiceCtx.NON_SSL_LOGIN); Context initialContext = new InitialContext(env); AuroraTransactionService.initialize (initialContext, "sess_iiop://localhost:2481:ORCL");
JTS contains methods that a client-side or server-side object uses to begin transactions, commit or roll back a transaction, and perform utility functions such as setting the transaction timeout. JTS methods should be used in CORBA clients server objects.
The following sections describe the JTS APIs:
To use the JTS methods, include the following import statements in your source:
import oracle.aurora.jts.util.TS; import javax.jts.util.*; import org.omg.CosTransactions.*;
The oracle.aurora.jts.util
package is included in the library file aurora_client.jar
, which must be in the CLASSPATH for all Java sources that use the JTS.
You use the static methods in the TS
class to retrieve the transaction service.
The JTS includes the following methods:
public static synchronized TransactionService getTS()
getTS
method returns a transaction service object.
getCurrent()
on it to return a Current
pseudo-object, the transaction context.
Current
pseudo-object.
Here is an example that begins a new transaction on a client, starting with getting the JNDI initial context:
import oracle.aurora.jndi.sess_iiop.ServiceCtx; import oracle.aurora.jts.client.AuroraTransactionService; import javax.naming.Context; import javax.naming.InitialContext; import java.util.Hashtable; ... Context ic = new InitialContext(env); ... AuroraTransactionService.initialize(ic, serviceURL); ... Employee employee = (Employee)ic.lookup (serviceURL + objectName); EmployeeInfo info; oracle.aurora.jts.util.TS.getTS().getCurrent().begin();
If there is no transaction service available, then getTS()
throws a NoTransactionService
exception.
The methods that you can call to control transactions on the current transaction context are the following:
public void begin()
Begins a new transaction.
Can throw these exceptions:
NoTransactionService
--if you have not initialized a transaction context.
SubtransactionsUnavailable
--if you invoke a begin()
before the current transaction has been committed or rolled back.
See the section "TransactionService" for information about initialization.
public Control suspend()
Suspends the current transaction in the session. Returns a Control
transaction context pseudo-object. You must save this object reference for use in any subsequent resume()
invocations. Invoke suspend()
in this way:
org.omg.CosTransactions.Control c = oracle.aurora.jts.util.TS.getTS().getCurrent().suspend();
suspend()
can throw these exceptions:
NoTransactionService
--if you have not initialized a transaction context.
TransactionDoesNotExist
--if not in an active transaction context. This can occur if a suspend()
follows a previous suspend()
, with no intervening resume()
.
If suspend()
is invoked outside of a transaction context, then a NoTransactionService
exception is thrown. If suspend()
is invoked before begin()
has been invoked, or after a suspend()
, the a exception is thrown.
public void resume(Control which)
Resumes a suspended transaction. Invoke this method after a suspend()
, in order to resume the specified transaction context. The which
parameter must be the transaction Control
object that was returned by the previous matching suspend()
invocation in the same session. For example:
org.omg.CosTransactions.Control c = oracle.aurora.jts.util.TS.getTS().getCurrent().suspend(); ... // do some non-transactional work oracle.aurora.jts.util.TS.getTS().getCurrent().resume(c);
resume()
can throw:
InvalidControl
--if the which
parameter is not valid, or is null.
public void commit(boolean report_heuristics)
Commits the current transaction. Set the report_heuristics
parameter to false.
The report_heuristics
parameter is set to true for extra information on two-phase commits. Because this release of Oracle9i does not support the two-phase commit protocol for distributed objects for JTS, use of the report_heuristics
parameter is not meaningful. It is included for compatibility with future releases.
commit()
can throw:
HeuristicMixe
d--if report_heuristics
was set true, and a two-phase commit is in progress.
HeuristicHazard
--if report_heuristics
was set true, and a two-phase commit is in progress.
The HeuristicMixe
d and HeuristicHazard
exceptions are documented in the OTS specification.
If there is no active transaction, commit()
throws a NoTransaction
exception.
public void rollback()
Rolls back the effects of the current transaction.
Invoking rollback()
has the effect of ending the transaction, so invoking any JTS method except begin()
after a rollback()
throws a NoTransaction
exception.
If not in a transaction context, rollback()
throws the NoTransaction
exception.
public void rollback_only() throws NoTransaction {
rollback_only()
modifies the transaction associated with the current thread so that the only possible outcome is to roll back the transaction. If not in a transaction context, rollback_only()
throws the NoTransaction
exception.
public void set_timeout(int seconds)
This method is not supported, and has no effect if invoked. The default timeout value is 60 seconds in all cases.
public Status get_status()
You can call get_status()
at any time to discover the status of the current transaction. Possible return values are:
javax.transaction.Status.StatusActive
javax.transaction.Status.StatusMarkedRollback
javax.transaction.Status.StatusNoTransaction
The complete set of status ints is defined in javax.transaction.Status
.
public String get_transaction_name() {
Invoke get_transaction_name()
to see the name of the transaction, returned as a String. If this method is invoked before a begin()
, after a rollback()
, or outside of a transaction context, it returns a null string.
Information on the Java Transaction Service is available at:
http://java.sun.com:/products/jts/index.html
The Sun JTS specification is available at:
http://java.sun.com/products/jta/index.html
The OTS specification is part of the CORBA services specification. Chapter 10 (individually downloadable) contains the OTS specification. Get it at:
http://www.omg.org/library/csindx.html
If you are using JDBC calls in your CORBA server object to update a database, and you have an active transaction context, you should not also use JDBC to perform transaction services, by calling methods on the JDBC connection. Do not code JDBC transaction management methods. For example:
Connection conn = ... ... conn.commit(); // DO NOT DO THIS!!
Doing so will cause a SQLException
to be thrown. Instead, you must commit using the UserTransaction
object retrieved to handle the global transaction. When you commit using the JDBC connection, you are instructing a local transaction to commit, not the global transaction. When the connection is involved in a global transaction, trying to commit a local transaction within the global transaction causes an error to occur.
In the same manner, you must also avoid doing direct SQL commits or rollbacks through JDBC. Code the object to either handle transactions directly using the UserTransaction
interface.
Within a global transaction, you cannot execute a local transaction. If you try, the following error will be thrown:
ORA-2089
"COMMIT is not allowed in a subordinate session
."
Some SQL commands implicitly execute a local transaction. All SQL DDL statements, such as "CREATE TABLE", implicitly starts and commits a local transaction under the covers. If you are involved in a global transaction that has enlisted the database that the DDL statement is executing against, the global transaction will fail.
|
Copyright © 1996-2001, Oracle Corporation. All Rights Reserved. |
|