26 Transaction Guard for Java

Oracle Database 12c Release 1 (12.1) introduces Transaction Guard feature that provides a generic infrastructure for at-most-once execution during planned and unplanned outages and duplicate submissions. This chapter discusses Transaction Guard for Java in the following sections:

Overview of Transaction Guard for Java

For the current applications, determining the outcome of the last commit operation in a guaranteed and scalable manner, following a communication failure to the server, is an unsolved problem. In many cases, the end users are asked to follow certain steps to avoid resubmitting duplicate request. For example, some applications warn users not to click the Submit button twice because if it is not followed, then users may unintentionally purchase the same items twice and submit multiple payments for the same invoice.

To solve this problem, Transaction Guard for Java provides transaction idempotence, that is, every transaction has at-most-once execution that prevents applications from submitting duplicate transactions. Every transaction is tagged with a Logical Transaction Identifier (LTXID), which can be used by the application after the occurrence of a failure to verify whether the transaction had committed before the failure or not. For example, if the commit calls do not return, then, using the LTXID, the application can find out whether it succeeded or not.

The Application Continuity for Java feature uses Transaction Guard for Java internally, which enables transparent session recovery and replay of SQL statements (queries and DMLs) since the beginning of the in-flight transaction. Application Continuity enables recovery of work after the occurrence of a planned or unplanned outage and Transaction Guard for Java ensures transaction idempotence. When an outage occurs, the recovery restores the state exactly as it was before the failure occurred.

Transaction Guard for Java APIs

This section discusses the APIs associated with Transaction Guard for Java for the following activities:

Retrieving the Logical Transaction Identifiers

Use the getLogicalTransactionId method of the oracle.jdbc.OracleConnection interface to retrieve the current Logical Transaction Identifiers that are sent by the server. This method call does not make a database round-trip.

Example

      OracleConnection oconn = (OracleConnection) ods.getConnection();
      ...
      // Getting the 1st LTXID after connecting
      LogicalTransactionId firstLtxid = oconn.getLogicalTransactionId();

Retrieving the Updated Logical Transaction Identifiers

Use the oracle.jdbc.LogicalTransactionIdEventListener interface for receiving updates to Logical Transaction Identifiers. You must implement this interface in your application to process the Logical Transaction Identifier events.

Registering Event Listeners

Use the addLogicalTransactionIdEventListener method to register a listener to the Logical Transaction Identifier events.

Example

      OracleConnection oconn = (OracleConnection) ods.getConnection();
      ...
      // The subsequent LTXID updates can be obtained through the listener
      oconn.addLogicalTransactionIdEventListener(this);

You can also use the addLogicalTransactionIdEventListener(LogicalTransactionIdEventListener listener, java.util.concurrent.Executor executor) method to register a listener with an executor.

Unregistering Event Listeners

Use the removeLogicalTransactionIdEventListener method to unregister a listener from the Logical Transaction Identifier events.

Example

      OracleConnection oconn = (OracleConnection) ods.getConnection();
      ...
      // The subsequent LTXID updates can be obtained through the listener
      oconn.removeLogicalTransactionIdEventListener(this);

Using Transaction Guard APIs: Complete Example

The following is a complete example using the Transaction Guard APIs.

      import oracle.jdbc.pool.OracleDataSource;
      import oracle.jdbc.OracleConnection;
      import oracle.jdbc.LogicalTransactionId;
      import oracle.jdbc.LogicalTransactionIdEvent;
      import oracle.jdbc.LogicalTransactionIdEventListener;
      
      public class transactionGuardExample
      {
            ...
            ...
            OracleDataSource ods = new OracleDataSource();
            ods.setURL(url);
            ods.setUser("user");
            ods.setPassword("password");
 
            OracleConnection oconn = (OracleConnection) ods.getConnection();
 
            // Getting the 1st LTXID after connecting
            LogicalTransactionId firstLtxid = oconn.getLogicalTransactionId();
 
            // The subsequent LTXID updates can be obtained via the listener
            oconn.addLogicalTransactionIdEventListener(this);
        }
 
      public class LtxidListenerImpl
        implements LogicalTransactionIdEventListener
      {
        ...
 
        public void onLogicalTransactionIdEvent(LogicalTransactionIdEvent ltxidEvent)
        {
          LogicalTransactionId newLtxid = ltxidEvent.getLogicalTransactionId();
          // process newLtxid ......
        }
      }

Using Server-Side Transaction Guard APIs

The DBMS_APP_CONT package contains the GET_LTXID_OUTCOME procedure that contains the server-side Transaction Guard APIs. This procedure forces the outcome of a transaction. If the transaction is not committed, then a fake transaction is committed. Otherwise, the state of the transaction is returned. By default, the EXECUTE privilege for this package is granted to Database Administrators.

Syntax

  PROCEDURE GET_LTXID_OUTCOME(CLIENT_LTXID        IN  RAW,
                              committed           OUT BOOLEAN,
                              USER_CALL_COMPLETED OUT BOOLEAN);

Input Parameter

CLIENT_LTXID specifies the LTXID from the client driver.

Output Parameter

COMMITTED specifies that the transaction is committed.

USER_CALL_COMPLETED specifies that the user call, which committed the transaction, is complete.

Exceptions

SERVER_AHEAD is thrown when the server is ahead of the client. So, the transaction is an old transaction and must have already been committed.

CLIENT_AHEAD is thrown when the client is ahead of the server. This can only happen if the server is flashed back or the LTXID is corrupted. In either of these situations, the outcome cannot be determined.

ERROR is thrown when an error occurs during processing and the outcome cannot be determined. It specifies the error code raised during the execution of the current procedure.

Example

Example 26-1 shows how you can call the GET_LTXID_OUTCOME procedure and find out the outcome of an LTXID:

Example 26-1 Finding Out the Outcome of an LTXID

    ...
    OracleConnection oconn = (OracleConnection) ods.getConnection();
    LogicalTransactionId ltxid = oconn.getLogicalTransactionId();
    boolean committed = false;
    boolean call_completed = false;
 
    try
    {
      CallableStatement cstmt = oconn.prepareCall(GET_LTXID_OUTCOME);
      cstmt.setObject(1, ltxid);
      cstmt.registerOutParameter(2, OracleTypes.BIT);
      cstmt.registerOutParameter(3, OracleTypes.BIT);
 
      cstmt.execute();
 
      committed = cstmt.getBoolean(2);
      call_completed = cstmt.getBoolean(3);
 
      System.out.println("LTXID committed ? " + committed);
      System.out.println("User call completed ? " + call_completed);
    }
    catch (SQLException sqlexc)
    {
      System.out.println("Calling GET_LTXID_OUTCOME failed");
      sqlexc.printStackTrace();
    }