Oracle9i Enterprise JavaBeans Developer's Guide and Reference Release 1 (9.0.1) Part Number A90188-01 |
|
In Oracle9i, Enterprise JavaBeans use Java Transaction API (JTA) 1.0.1 for managing transactions. JTA provides the ability for both bean-managed and container-managed 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.
Note that this chapter only covers global transactions involving EJBs. For pure JDBC clients and CORBA applications involved in JTA, see the appropriate CORBA and JDBC manuals.
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.
UserTransaction
object, which is retrieved using a JNDI lookup. The UserTransaction
object contains begin
, commit
, and rollback
methods for controlling the transaction demarcation.
The following are the methods that you can use for client-side or programmatic server-side 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:
The transaction is managed automatically by the container, which begins and ends the transaction depending on the configuration for the bean within the deployment descriptor. All transactional behavior is executed by the container based on the transactional attribute specified in the EJB deployment descriptor. The following table briefly describes the transaction attribute types that should be specified in the bean's deployment descriptor:
Table 7-1 Transaction Attributes
See the following for more information:
The bean methods control the transaction demarcation with the UserTransaction
object, similar to client-side demarcation. The difference is that the bean retrieves the UserTransaction
object through either an in-session JNDI lookup or retrieves it from the SessionContext
object.
If the bean specifies itself as bean-managed transactional, it has the following responsibility:
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. However, the invoked object must be configured to support transactions and accept this propagation for it to be included in the 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 the server object supports transactions, this object is attached to the transaction manager as involved in the global transaction. 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.
A bean-managed transactional bean can only initiate transactions, it cannot become involved in an existing transaction. This means that it cannot accept a transactional context from any other object--client or server.
The definition of the transaction attribute within the EJB deployment descriptor for container-managed transactional beans determines how the global transaction context is propagated to the server object. You must define one of these attributes for the transaction for a container-managed transactional bean. The following table lists each transaction attribute and the behavior that occurs for each server object type and for any resources.
Each resource, such as an Oracle database, that you want managed in the global transaction must be enlisted in that transaction. The transaction manager tracks all resources involved in the global transaction. Database resources can be included in the global transaction in one of two ways:
<default-enlist>
as TRUE, the transaction manager automatically includes the database where this bean resides in the transaction.
DataSource
(OracleJTADataSource
) is bound into the JNDI namespace for a resource, and this DataSource
is retrieved within the context of a transaction, the database is included in the transaction.
When the bean is deployed to the database, it can contain an element called <default-enlist>
. If this element is set to TRUE
(default is FALSE
), then when the bean is invoked, the database that this bean resides in will be included in the global transaction. If FALSE
, then the only way that the database local to the bean can be included in the transaction is through explicit enlistment--which is described in the next section.
Set the <default-enlist>
element in the Oracle-specific deployment descriptor. This element specifics whether the local resource is automatically enlisted in the global transaction or not.
DataSource
object.
Notice that if you ask for default enlistment of the local database and you include any other database in the transaction, a two-phase commit scenario applies. See "Configuring Two-Phase Commit Engine" for more information.
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. All databases are enlisted when JDBC connections are retrieved from JTA DataSource
objects. See "Enlisting Resources" for more information on database enlistment.
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-4 Environment Setup For Transactional Object Retrieval
There are two basic methods for enlisting the database, as shown in Table 7-5.
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-7 summarizes the single-phase commit scenario. It covers the JNDI binding requirements and the application implementation runtime requirements.
Table 7-8 summarizes the two-phase commit scenario.
Table 7-9 encapsulates the differences between container and bean-managed transactions for the single-phase commit scenario. It covers the differences within the deployment descriptors, the JNDI binding requirements, and the application implementation runtime requirements.
Aspect |
Container-Managed Transaction Allowed for entity and session beans. |
Bean-Managed Transaction Allowed only for session beans. |
---|---|---|
Deployment Descriptor |
|
|
Binding |
|
|
Runtime |
|
For the two-phase commit scenario, both bean-managed and container-managed transactions include the same requirements as designated in the single-phase scenario (shown in Table 7-9). In addition, both types of transactions must also do the extra requirements designated in Table 7-10.
Server-side demarcation can be performed either through a container-managed or bean-managed transactions. A container-managed transaction is specified within the EJB deployment descriptor for the bean. Most EJBs use container-managed transactions as it requires no transaction logic within the application.
No matter which method that the server object decides to demarcate transactions, the enlistment of the database is the same for both container and bean-managed transactions.
You can declare that the container manages the transaction for the bean within the EJB deployment descriptor. Based upon the transaction attribute you specify within the EJB deployment descriptor, the container will begin, commit, or rollback global transactions when a method in the bean instance is invoked. Each bean can be specified with different transaction attributes. The transaction attributes specify how the transaction demarcation is handled. This means that your bean does not retrieve the UserTransaction
object, nor invokes any of its methods. The container does this for you. To enable container-managed transactions within your session or entity bean, do the following:
Container
" within the <transaction-type>
element in the deployment descriptor.
<container-transaction>
element in the deployment descriptor.
<default-enlist>
element to TRUE
--If you set the <default-enlist>
element to TRUE
within the Oracle-specific deployment descriptor, then the database where the bean resides is automatically enlisted when the bean is included in the transaction. This should only be used if the local database is the only database involved in the transaction.
Normally, you want the database that is local to the EJB to be enlisted. However, if the EJB is deployed to the Oracle9i Application Server, you do not want the local resource, the Oracle Database Cache, to be enlisted.
The default value for this element is FALSE.
DataSource
object--You must retrieve a previously bound JTA DataSource
object for the database within the context of a transaction. Retrieving the JDBC connection to this object enlists the database in the transaction.
DataSource
that represents your database within the namespace with the bindds
command. Remember that all DataSource
objects must be bound with the -dstype
jta
for inclusion in the transaction.
getConnection
method of the DataSource
object.
Optionally, for easier retrieval within your code, you can specify bound DataSource
objects within the <resource-ref>
elements in the deployment descriptor.
To bind a DataSource
object for a single-phase commit transaction with the empHost
database to the name "/test/DataSource/empDS
" in the namespace located on nsHost
, execute the following:
sess_sh -service jdbc:oracle:thin:@nsHost:5521:ORCL
-user SCOTT -password TIGER
&bindds /test/DataSource/empDS -url jdbc:oracle:thin:@empHost:5521:ORCL
-dstype jta
After binding the DataSource
object in the namespace, the server can retrieve this object from JNDI. If retrieved within a global transaction, the getConnection
method causes this database to be enlisted in the transaction. See "Bind DataSource Object in the Namespace" for more information.
The EmployeeBean
is specified as a container-managed transaction bean with the RequiresNew
attribute. Also, for easy retrieval, the JDBC database resource objects are defined as environment variables.
<?xml version="1.0"?> <!DOCTYPE ejb-jar PUBLIC "-//Sun Microsystems Inc.//DTD Enterprise JavaBeans 1.1 //EN" "ejb-jar.dtd"> <ejb-jar> <enterprise-beans> <session> ...<transaction-type>Container</transaction-type>
<resource-ref>
<res-ref-name>jdbc/EmployeeDS</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Application</res-auth>
</resource-ref>
</session> </enterprise-beans> <assembly-descriptor> ...<container-transaction>
<method>
<ejb-name>test/EmployeeBean</ejb-name>
<method-name>*</method-name>
</method>
<trans-attribute>RequiresNew</trans-attribute>
</container-transaction>
</assembly-descriptor> </ejb-jar>
<default-enlist>
element is set to TRUE.
<?xml version="1.0"?> <!DOCTYPE oracle-descriptor PUBLIC "-//Sun Microsystems Inc.//DTD Enterprise Jav aBeans 1.1//EN" "oracle-ejb-jar.dtd"> <oracle-ejb-jar> <oracle-descriptor> <mappings> ...<resource-ref-mapping>
<res-ref-name>jdbc/EmployeeDS</res-ref-name>
<jndi-name>test/DataSource/empDS</jndi-name>
</resource-ref-mapping>
<transaction-manager>
<default-enlist>true</default-enlist>
</transaction-manager>
</mappings> </oracle-descriptor> </oracle-ejb-jar>
With container-managed transactions, you do not need to retrieve a UserTransaction
object to start and stop the transaction. Instead, the container will demarcate the transaction based upon the <trans-attribute>
element within the EJB deployment descriptor. For the EmployeeBean
example, the <trans-attribute>
was set to RequiresNew
. The container will start a new transaction when this bean is invoked.
The <default-enlist>
element was set to TRUE, so the database where the bean resides is automatically enlisted in the global transaction. You do not need to retrieve its DataSource
in order to enlist the database. If you can use SQLJ for updating the local database, you will not need to retrieve a DataSource
for the SQL statements to be included in the transaction. However, if you need to use JDBC for updating the local database, you retrieve the JDBC connection in the same manner as for enlisting a remote database--by retrieving a bound JTA DataSource
from the JNDI provider and using getConnection
to retrieve the JDBC connection to that database.
Since the SQLJ statement alone is not an interesting example, the following shows a container-managed transactional bean that retrieves the JDBC connection to the local database for executing JDBC statements against. The local database is bound in the JNDI namespace with a JTA DataSource
that is identified by the EJB environment variable "jdbc/EmployeeDS
". From the retrieved DataSource
, it retrieves a connection to the remote Oracle9i database. Since the database is local, no username and password is required in the getConnection
method. If the database was remote, you would need to provide authentication information. Lastly, remember to close all connections to the database once the statements are completed.
Notice that no UserTransaction
object is necessary for demarcating the transaction.
Context ic = new InitialContext (); //retrieve the DataSource to the local database - this database has already //been enlisted. Just retrieving a JDBC connection for performing JDBC work. DataSource empDS = (DataSource)ic.lookup("java:comp/env/jdbc/EmployeeDS"); // get a connection to the local database, no need for username/password Connection empConn = empDS.getConnection(); //perform JDBC work given the empConn object... ... //close database connection empConn.close();
Only session beans have the option to use bean-managed transactions. This means that only session beans can demarcate the transaction with the begin
, commit
, and rollback
methods. Thus, the bean programmatically starts and stops the transaction. All resources are enlisted when you retrieve connections to DataSource
objects that have been bound correctly within the JNDI namespace.
In order to specify that this session bean is going to programmatically demarcate its transaction, it must do the following:
Bean
" within the <transaction-type>
element in the deployment descriptor. See "Defining Transactions" for full instructions.
DataSource
that represents your database within the namespace as indicated within Table 7-6. See "Bind DataSource Object in the Namespace" for more information.
Optionally, for easier retrieval within your code, you can specify bound DataSource
objects within the <resource-ref>
elements in the deployment descriptor.
<default-enlist>
value.
UserTransaction
interface to programmatically start and stop the transaction. See "UserTransaction Interface" for more information.
For all session beans that are bean-managed transactional, specify the following in the EJB deployment descriptor:
<enterprise-beans>
<session>
. . .
<transaction-type>Bean</transaction-type>
</session>
</enterprise-beans>
Since this is deployed to an Oracle9i database, set the <default-enlist>
element is to TRUE in the Oracle-specific deployment descriptor.
<?xml version="1.0"?> <!DOCTYPE oracle-descriptor PUBLIC "-//Sun Microsystems Inc.//DTD Enterprise Jav aBeans 1.1//EN" "oracle-ejb-jar.dtd"> <oracle-ejb-jar> <oracle-descriptor> <mappings> ...<transaction-manager>
<default-enlist>true</default-enlist>
</transaction-manager>
</mappings> </oracle-descriptor> </oracle-ejb-jar>
Note: See "Defining Transactions" for a full description of defining transaction management in the EJB deployment descriptor. |
The session bean implementation demarcates transactions in the same manner as a client would. It invokes the begin
, commit
, and rollback
methods off of the UserTransaction
object. The only difference is how the UserTransaction
object is retrieved. There are two methods for retrieving the UserTransaction
object on the server side in a bean-managed transactional bean:
The bean retrieves the UserTransaction
object from the SessionContext
object, which was set within the setSessionContext
method. The container has already retrieved the UserTransaction
object for you. The following shows that the session context is saved in the ctx
variable.
public void setSessionContext (SessionContext ctx) { this.ctx = ctx; }
Within the bean implementation, retrieve the UserTransaction
from the session context and begin the transaction.
UserTransaction ut = ctx.getUserTransaction(); ut.begin(); ... ut.commit();
The EJB 1.1 method retrieves the UserTransaction
object by performing an in-session lookup with the following JNDI name: "java:comp/UserTransaction
". In a single-phase commit environment, the container will create the UserTransaction
object for you; in a two-phase environment, you must have already bound a UserTransaction
object with the two-phase commit information (username, password, and two-phase commit URL) into the namespace. After retrieval, you use this object to demarcate your global transaction. See "Bind UserTransaction Object in the Namespace" for more information.
The following demonstrates retrieving a UserTransaction
object from the namespace:
ic = new InitialContext ( );
// lookup the usertransaction
UserTransaction ut = (UserTransaction)ic.lookup ("java:comp/UserTransaction");
ut.begin ();
...
ut.commit();
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--which, in this example, is a container-managed transactional bean. The client starts a global transaction, then invokes the bean. Since the bean is a container-managed transactional bean and is specified with the transactional attribute of "Supports
", the transactional context is propagated to include the server object. If we assume that the bean has defined <default-enlist>
to be TRUE, then the database is also automatically enlisted in the transaction.
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()
.
Example7-5 shows a client that invokes a server bean 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.
//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";
//User and password are case sensitive. String user = "SCOTT"; String password = "TIGER";//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, user); env.put (Context.SECURITY_CREDENTIALS, password); 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
UserTransaction ut = (UserTransaction)ic.lookup ("jdbc_access://test/myUT");
//3. Start the transaction
ut.begin();
//4. Retrieve the EJB
// get an handle to the employee_home object EmployeeHome employee_home = (EmployeeHome)ic.lookup ("sess_iiop://myhost:1521:orcl/test/employee"); // get an handle to the remote bean Employee employee = employee_home.create ();//5. Perform bean 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()
.
Example7-6 shows a client that invokes a bean 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";
//User and password are case sensitive. String user = "SCOTT"; String password = "TIGER";//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, user); env.put (Context.SECURITY_CREDENTIALS, password); 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
UserTransaction ut;
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 EJB
// get an handle to the employee_home object EmployeeHome employee_home = (EmployeeHome)ic.lookup (serviceURL + objectName); // get an handle to the remote bean Employee employee = employee_home.create ();//6. Perform bean business logic.
...//7. Close the database connection.
conn.close ();//8. End the transaction
//Commit the updated value ut.commit();
Whether your bean instance uses bean-managed or container-managed transactions, the databases that the bean 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".
Once bound, you can enlist the database after the transaction begins.
The local resource--whether it is an Oracle9i database or an Oracle9i Application Server database cache--is automatically enlisted in the transaction when the <default-enlist>
element is set to TRUE. You can only enlist using this element when you are in a single-phase commit scenario. That is, the local resource is the only resource involved in the transaction. If you have other databases involved in this transaction, you can only use the explicit method of enlistment by binding a JTA DataSource
object for each database.
The <default-enlist>
value describes whether the resource local to the EJB should be automatically enlisted within the transaction or not. That is, the database or database cache resource where the bean resides can be automatically enlisted in the transaction unless specified not to. Normally, you want the database that is local to the EJB to be enlisted. However, if the EJB is deployed to the Oracle9i Application Server, you do not want the local resource, the Oracle Database Cache, to be enlisted.
If the resource is automatically enlisted within the transaction, all SQL commands executed against this database are committed when the transaction is committed.
As discussed in the Oracle9i SQLJ Developer's Guide and Reference, an implicit local connection is supplied to the database that the object is running in. Any statements executed within the SQLJ statement is executed against the local database. However, in order for the statement results to be part of the transaction, the |
|
You can create a connection by retrieving the JTA |
Always execute these methods and subsequent SQL statements after the global transaction has started.
The following example is a container-managed transactional bean with RequiresNew
attribute, so the global transaction is initialized when the bean is first invoked. The local connection is retrieved through the DataSource.getConnection
method, which enlists the database in the transaction. SQL statements are executed against the local database. These statements are committed when the global transaction is committed by the container when the bean exits.
The JTA DataSource
must have been bound previously in the JNDI namespace. In this case, the bindds
would have been for the local database with the KPRB driver, as follows:
bindds /test/myDB -url jdbc:oracle:kprb: -dstype jta
The EJB deployment descriptor defines the DataSource as an environment variable, as follows:
<ejb-jar> <enterprise-beans> <session> ...<resource-ref>
<res-ref-name>jdbc/EmployeeDS</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Application</res-auth>
</resource-ref>
</session> </enterprise-beans>
The Oracle-specific descriptor maps "jdbc/EmployeeD
S" to its JNDI bound name "/test/myDB
".
<oracle-ejb-jar> <oracle-descriptor> <mappings> ...<resource-ref-mapping>
<res-ref-name>jdbc/EmployeeDS</res-ref-name>
<jndi-name>test/myDB</jndi-name>
</resource-ref-mapping>
... </mappings> </oracle-descriptor> </oracle-ejb-jar>
After binding, the bean can retrieve and use this object, as follows:
public EmpRecord query (int empNumber) throws SQLException, RemoteException {//Retrieving the UserTransaction and DataSource using in-session activation
Context ic = new InitialContext ( ); //Retrieve the DataSource using in-session activation DataSource ds = (DataSource) ic.lookup("java:comp/env/jdbc/EmployeeDS"); //Retrieve the local connection object to the local database Connection conn = ds.getConnection ();//prepare and execute a sql statement against the local database. PreparedStatement ps = conn.prepareStatement ("select ename, sal from emp where empno = ?"); ...//do work ps.close(); //close the database connection conn.close(); }
You can perform the previous function with SQLJ in the case where the database is enlisted automatically with <default-enlist>
or where the database is explicitly enlisted with the DataSource
object.
Alternatively to Example7-7, you can automatically enlist the local database with the <default-enlist>
element and use SQLJ for updating the database.
The following example is a container-managed transactional bean with RequiresNew
attribute, so the global transaction is initialized when the bean is first invoked. In addition, the local database is automatically enlisted because the Oracle-specific deployment descriptor defines <default-enlist>
to be TRUE.
SQLJ statements are executed against the local database. These statements are committed when the global transaction is committed by the container when the bean exits.
public EmpRecord query (String name) throws SQLException, RemoteException { int empno = 0; double salary = 0.0; #sql { select empno, sal into :empno, :salary from emp where ename = :name }; System.out.println (" " + name + " sal = " + salary); }
With the same deployment descriptors that are described in Example7-7, 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.
public EmpRecord query (int empNumber) throws SQLException, RemoteException
{
//Retrieving the UserTransaction and DataSource using in-session activation
Context ic = new InitialContext ( );
//Retrieve the DataSource using in-session activation
DataSource ds = (DataSource) ic.lookup("java:comp/env/jdbc/EmployeeDS");
//Retrieve the local connection object to the local database
Connection conn = ds.getConnection ();
//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 database connection
conn.close();
}
If you access a remote 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 is a container-managed transactional bean, so it does not retrieve the UserTransaction
. However, it does retrieve a DataSource
to start a JDBC 2.0 connection.
// 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 ("/test/myDB");
// 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();
return new EmployeeInfo (name, empno, salary); }
For most global transactions, you will need to bind at least one of the following objects in the namespace:
UserTransaction
object--Necessary for all client-demarcated and bean-managed transactions.
DataSource
object--Necessary for specifying databases that will be included in the transaction.
Notice that if you have a container-managed transaction that only includes beans in the transaction--and no database resources--you do not need to bind either one of these objects in the namespace.
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 a session bean that uses bean-demarcated transactions.
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
<transaction-manager>
element. This element only needs to be defined in the bean where the transaction is started. See "Defining Oracle-Specific Elements for Transactions" for more information.
Once configured, the actual code for starting transactions and enlisting databases in the transaction is the same as described in previous sections. The only difference is that you can open connections to more than a single database. Figure 7-5 shows the client invoking EmployeeBean
, which opens a connection to its local database and a connection to a remote database.
The client initializes its environment, retrieves the EJB reference, and invokes the remote EmployeeBean
EJB. The client does not start the transaction. When the client invokes the EmployeeBean
, the container starts the transaction.
package client; import common.*; import java.util.Hashtable; import javax.naming.Context; import javax.naming.InitialContext; import oracle.aurora.jndi.sess_iiop.ServiceCtx; public class Client { public static void main (String[] args) throws Exception { String serviceURL = args [0]; String jdbcURL = args [1]; String objectName = args [2]; String user = args [3]; String password = args [4]; // set up 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); // lookup the home and remote interfaces fro the employee EmployeeHome home = (EmployeeHome)ic.lookup (serviceURL + objectName); EmployeeRemote remote = home.create (); // retrieve info abount this employee in this session Employee employee = remote.getEmployeeForUpdate ("SCOTT"); System.out.println ("Beginning salary for " + employee.name + " is " + employee.salary); // increase salary // employee.salary += 0.1 * employee.salary; employee.salary += 100; // update the infomation in the transaction remote.updateEmployee (employee); } }
The EmployeeBean
is deployed as a container-managed transaction bean with the RequiresNew
attribute. Both of the JDBC resources contained within this transaction are specified in the <resource-ref>
elements. The following is the portion of the EJB deployment descriptor that relates to transactions:
<?xml version="1.0"?> <!DOCTYPE ejb-jar PUBLIC "-//Sun Microsystems Inc.//DTD Enterprise JavaBeans 1.1 //EN" "ejb-jar.dtd"> <ejb-jar> <enterprise-beans> <session> ...<transaction-type>Container</transaction-type>
<resource-ref>
<res-ref-name>jdbc/EmployeeDS</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Application</res-auth>
</resource-ref>
<resource-ref>
<res-ref-name>jdbc/HRDS</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Application</res-auth>
</resource-ref>
</session> </enterprise-beans> <assembly-descriptor> ...<container-transaction>
<method>
<ejb-name>test/EmployeeBean</ejb-name>
<method-name>*</method-name>
</method>
<trans-attribute>RequiresNew</trans-attribute>
</container-transaction>
</assembly-descriptor> </ejb-jar>
<resource-ref>
elements defined in the EJB deployment descriptor are mapped to the JNDI bound names in the Oracle-specific deployment descriptor.
Both of the databases are bound as JTA DataSource
objects in the JNDI namespace, as follows:
bindds /test/DataSource/empDS -url jdbc:oracle:kprb:@empHost:5521:ORCL
-dstype jta -dblink 2pcToEmp.oracle.com bindds /test/DataSource/hrDS -url jdbc:oracle:thin:@HrHost:5521:ORCL
-dstype jta -dblink 2pcToHR.oracle.com
UserTransaction
that is bound with the two-phase commit engine URL is specified in the <transaction-manager>
element.
The UserTransaction
object was previously bound with the bindut
command. For example, the following binds "/test/myUT
" for this two-phase commit:
bindut /test/myUT -url sess_iiop://dbsun.mycompany.com:2481:ORCL
<default-enlist>
element must be set to FALSE. The only way to enlist a local database in a two-phase commit scenario is to use a JTA DataSource
object.
<?xml version="1.0"?> <!DOCTYPE oracle-descriptor PUBLIC "-//Sun Microsystems Inc.//DTD Enterprise Jav aBeans 1.1//EN" "oracle-ejb-jar.dtd"> <oracle-ejb-jar> <oracle-descriptor> <mappings> ...<resource-ref-mapping>
<res-ref-name>jdbc/EmployeeDS</res-ref-name>
<jndi-name>test/DataSource/empDS</jndi-name>
</resource-ref-mapping>
<resource-ref-mapping>
<res-ref-name>jdbc/HRDS</res-ref-name>
<jndi-name>test/DataSource/hrDS</jndi-name>
</resource-ref-mapping>
<transaction-manager>
<jndi-name>test/myUT</jndi-name>
<default-enlist>false</default-enlist>
</transaction-manager>
</mappings> </oracle-descriptor> </oracle-ejb-jar>
The container-managed transactional EmployeeBean
retrieves connections to both the local and a remote Oracle9i database within the updateEmployee
method.
public class EmployeeBean implements SessionBean { Employee remoteEmployee = null; String remoteEmpName = "SMITH"; ... public void updateEmployee (Employee employee) throws SQLError, RemoteException { Context ic = new InitialContext(); // inSession Lookup Context // get a connection to the local DB using an environment variable //specified in the deployment descriptorDataSource localDS = (DataSource)ic.lookup
("java:comp/env/jdbc/EmployeeDS");
// get a connection to the local DBConnection localConn = localDS.getConnection ();
//retrieve the remote database DataSource HRDS using the environment //variable specified in the deployment descriptorDataSource remoteDS = (DataSource)ic.lookup ("java:comp/env/jdbc/HRDS");
// get a connection to the remote DB passing in the username and // password for this database. (Otherwise, it would have had to be // specified in the Context environment.Connection remoteConn =
remoteDS.getConnection ("scott", "tiger");
//setup the context for issuing SQLJ against the remote databaseDefaultContext remoteCtx = new DefaultContext (remoteConn);
//issue SQL DML statements against the local database
#sql { update emp set ename = :(employee.name), sal = :(employee.salary)
where empno = :(employee.number) };
remoteEmployee.salary += 200;//issue SQL DML statements against the remote database
#sql [remoteCtx] { update emp set ename = :(remoteEmployee.name),
sal = :(remoteEmployee.salary)
where empno = :(remoteEmployee.number) };
//close both database connectionslocalConn.close();
remoteConn.close ();
}
In a normal global transaction, you open connections to each database that you want included in the database. After the transaction completes, the transaction manager commits all changes to all databases involved in the transaction.
However, with Oracle9i Application Server, the Oracle Database Cache may incorrectly be treated by the transaction manager as one of the databases in the global transaction. A typical application is shown in Figure 7-6. The EJB that is active in the middle-tier retrieves a connection to both the Oracle Database Cache and to the back-end Oracle database. However, the EJB can only update the back-end database. The transaction manager must not treat the database cache as another database involved in the transaction or it will perform a two-phase commit when the transaction ends. The two-phase commit process is expensive and unnecessary. Only the back-end database should be enlisted in the global transaction; thus, prompting the transaction manager to perform a single-phase commit, which is inexpensive.
To ensure that the database cache is not treated as an enlisted database in the global transaction, do the following:
<default-enlist>
element in the Oracle-specific deployment descriptor is either not set--so that it defaults to FALSE--or is set to FALSE.
DataSource
for the database cache with any non-JTA type. Only a JTA DataSource
(OracleJTADataSource
) object can be automatically enlisted in a global transaction. The DataSource
object bound for the database cache must be bound through the bindds
command with any -type
other than jta
. If bound with bindds
-type
jta
, the database cache will be considered part of the global transaction and the transaction manager will complete the global transaction with a two-phase 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 resource transaction automatically has an idle timeout of 60 seconds. If the database session attached to the transaction is idle for over the timeout limit, the transaction is rolled back. That is, if you close the connection to one of the databases in the transaction, the timeout starts at that point. If you have not completed the transaction by the time the timer is up, the transaction is rolled back. However, if you reopen another connection to the same database, then the timer stops. It will restart at zero when the reopened connection is closed.
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 ();
An EJB that is a session bean can optionally implement the session synchronization interface, to be notified by the container of the transactional state of the bean. The following methods are specified in the javax.ejb.SessionSynchronization
interface:
public abstract void afterBegin() throws RemoteException
The afterBegin()
method notifies a session Bean instance that a new transaction has started, and that the subsequent methods on the instance are invoked in the context of the transaction.
A bean can use this method to read data from a database and cache the data in the bean's fields.
This method executes in the proper transaction context.
public abstract void beforeCompletion() throws RemoteException
The container calls the beforeCompletion()
method to notify a session bean that a transaction is about to be committed. You can implement this method to, for example, write any cached data to the database.
public abstract void afterCompletion(boolean committed) throws RemoteException
The container calls afterCompletion()
to notify a session bean that a transaction commit protocol has completed. The parameter tells the bean whether the transaction has been committed or rolled back.
This method executes with no transaction context.
In order for the container to invoke your bean implementation before and after every transaction, your bean must implement the SessionSynchronization
interface.
package employeeServer; import employee.*; import javax.ejb.SessionBean; import javax.ejb.CreateException; import javax.ejb.SessionContext; import java.rmi.RemoteException; import java.sql.SQLException; public class EmployeeBean implements SessionBean
implements SessionSynchronization
{ // Methods of the Employee interface public EmployeeInfo getEmployee (String name) throws RemoteException, SQLException { int empno = 0; double salary = 0.0; #sql { select empno, sal into :empno, :salary from emp where ename = :name }; return new EmployeeInfo (name, empno, salary); } public void updateEmployee (EmployeeInfo employee) throws RemoteException, SQLException { #sql { update emp set ename = :(employee.name), sal = :(employee.salary) where empno = :(employee.number) }; return; } // Methods of the SessionBean public void ejbCreate () throws RemoteException, CreateException {} public void ejbRemove () {} public void setSessionContext (SessionContext ctx) {} public void ejbActivate () {} public void ejbPassivate () {}public void beforeBegin()
{ ... perform work ... }public void afterCompletion()
{ ... perform work ...
} }
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.
If you are using JDBC calls in your bean 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 bean to either handle transactions directly using the UserTransaction
interface or let the bean container manage the bean transactions.
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. |
|