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:
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.
See Also:
Oracle Database Development GuideThe 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.
See Also:
"Application Continuity for Java"This section discusses the APIs associated with Transaction Guard for Java for the following activities:
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.
OracleConnection oconn = (OracleConnection) ods.getConnection(); ... // Getting the 1st LTXID after connecting LogicalTransactionId firstLtxid = oconn.getLogicalTransactionId();
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.
Use the addLogicalTransactionIdEventListener
method to register a listener to the Logical Transaction Identifier events.
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.
Use the removeLogicalTransactionIdEventListener
method to unregister a listener from the Logical Transaction Identifier events.
OracleConnection oconn = (OracleConnection) ods.getConnection(); ... // The subsequent LTXID updates can be obtained through the listener oconn.removeLogicalTransactionIdEventListener(this);
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 ...... } }
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.
PROCEDURE GET_LTXID_OUTCOME(CLIENT_LTXID IN RAW, committed OUT BOOLEAN, USER_CALL_COMPLETED OUT BOOLEAN);
CLIENT_LTXID
specifies the LTXID
from the client driver.
COMMITTED
specifies that the transaction is committed.
USER_CALL_COMPLETED
specifies that the user call, which committed the transaction, is complete.
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 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(); }