Either the database is updated with all of a transaction's changes to persistent objects, or the database is not updated at all. If a failure occurs in the middle of a transaction, or you decide to abort the transaction, the contents of the database remain unchanged.
This chapter discusses the following topics:
Starting a Transaction
Handling Automatic Transaction Aborts
Determining Transaction Boundaries
Starting a Transaction
ObjectStore provides the COM.odi.Transaction class to represent a transaction. You should not make subclasses of this class.
This section discusses the following topics:
public static Transaction begin(int type)The transaction type determines whether ObjectStore waits for a database lock. There can be only one write lock on a database. There can be multiple read-only locks on a database.The type of the transaction can be ObjectStore.UPDATE or ObjectStore.READONLY.
If there is no open database when you start the current transaction, ObjectStore tries to obtain a read lock as soon as the session tries to open a database.
Transaction tr = Transaction.begin(ObjectStore.UPDATE);This example returns a Transaction object that represents the transaction just started. The result is stored in tr. This is an update transaction, which means that the application can modify database contents.
If you try to modify persistent data in a read-only transaction, ObjectStore throws UpdateReadOnlyException.
Difference Between Update and Read-Only Transactions
You can start a transaction for READONLY or for UPDATE. The only difference between the two types is that when you start a transaction for READONLY, ObjectStore performs additional checks during the transaction and when you commit the transaction. These checks ensure that changes are not saved in the database if they were made in a read-only transaction. There is no difference in performance between a read-only transaction and an update transaction. Working Inside a Transaction
A transaction is associated with the session that is associated with the thread that starts the transaction. A transaction remains active until you explicitly commit it or until it aborts. A session can have only one active transaction. Concurrent transactions must be in separate sessions.
Two transactions can never update the same object at the same time. However, two transactions can both open the same database for update at the same time and they can concurrently make updates to different parts of the database.
public Session Transaction.getSession()To obtain the transaction that is associated with the current session, call the Session.currentTransaction() method. The method signature is
public Transaction Session.currentTransaction()To determine whether or not there is a transaction in progress for the current session, call the Transaction.inTransaction() method on Transaction. The method signature is
public static boolean inTransaction()This method returns true if there is a transaction in progress for the current session. Otherwise, it returns false. It is worth noting that inTransaction() return false if the calling thread is not joined to the current session. This can be important if you use an unassociated thread to check whether there is a transaction and then try to close the database based on a false response. The previously unassociated thread would be automatically joined to the session to close the database. If a transaction is actually in progress, ObjectStore throws TransactionInProgressException, which is, of course, unexpected since inTransaction() returned false.
Obtaining Transaction Objects
An application can obtain the transaction object for the current thread by calling the static current() method on the Transaction class. The method signature is
public static Transaction current()This method returns the transaction object associated with the current session, for example:
Transaction.current().commit()This example commits the current transaction. If no transaction is in progress, current() throws NoTransactionInProgressException.
Performing a Transaction Checkpoint
You can use the Transaction.checkpoint() method to commit changes but continue working with the same persistent objects. When you call the checkpoint() method, you specify whether to retain persistent objects as hollow objects or make all persistent objects stale. This is useful when you are trying to improve concurrency, or when you are at a consistent state and want to save your changes but keep working. See Checkpoint: Committing and Continuing a Transaction.
Setting a Transaction Priority
When there is a deadlock, the Server uses the transaction priority as one of the criteria to determine which transaction to abort. See Helping Determine the Transaction Victim in a Deadlock.
Ending a Transaction
When transactions terminate successfully, they commit, and their changes to persistent objects are saved in the database. When transactions terminate unsuccessfully, they abort, and their changes to persistent objects are discarded.
Also, a transient referred-to object cannot be referenced from more than one segment. All cross-segment references must be to exported objects, which means you must have explicitly migrated the object to one of the segments and specified that it is exported. If ObjectStore finds an unexported object that is referred to from more than one segment, it throws AbortException.
Making objects stale
To commit a transaction and make the state of persistent objects stale, call the commit() method with no argument. The method signature is
public void commit()For example, tr.commit();
public void commit(int retain)The following example commits the transaction and specifies that the contents of the active persistent objects should remain available to be read.
tr.commit(ObjectStore.RETAIN_READONLY);
Open databases
If you opened any databases during the transaction, ObjectStore closes them. Any databases that were open before the aborted transaction was started remain open after the abort operation. Application failure
If an application fails during a transaction, when you restart the application the database is as it was before the transaction started. If an application fails during a transaction commit, when you restart the application either the database is as it was before the transaction that was being committed or the database reflects all the transaction's changes. This depends on how far along in the commit process the application was when it terminated. Either all or none of the transaction's changes are in the database. abort()
To abort a transaction and set the state of persistent objects to the state specified by Transaction.setDefaultAbortRetain(), call the abort() method. The default state is stale. The method signature is
public void abort()For example,
tr.abort();
public void abort(int retain)The following example aborts the transaction and specifies that the contents of the active persistent objects should remain available to be read.
tr.abort(ObjectStore.RETAIN_READONLY);
The superclass of exceptions for transaction abort is AbortException. The superclass of exceptions for which it makes sense to retry the aborted exception is RestartableAbortException. AbortExceptions that do not extend RestartableAbortException indicate that before the transaction can proceed, some action is probably required to correct the problem that caused the exception.
import COM.odi.*; class DeadlockTest { static final int MAX_RETRIES = 10; public static void main(String[] args) { ObjectStore.initialize(null, null); Database db = getDatabase(); while (true) { test(db); } } static void test(Database db) { int retries; for (retries = 0; retries < MAX_RETRIES; retries++) { try { Transaction.begin(ObjectStore.UPDATE); Integer value = increment(db); Transaction.current().commit(); System.out.println("Value = " + value + ", retries = " + retries); break; } catch (RestartableAbortException e) { } } if (retries >= MAX_RETRIES) System.out.println("Gave up after " + retries + " retries."); } static Database getDatabase() { try { return Database.open("test.odb", ObjectStore.UPDATE); } catch (DatabaseNotFoundException e) { Database db = Database.create("test.odb", 0664); Transaction.begin(ObjectStore.UPDATE); db.createRoot("root", null); Transaction.current().commit(); return db; } } static Integer increment(Database db) { Integer value = (Integer)db.getRoot("root"); if (value == null) value = new Integer(0); else value = new Integer(value.intValue() + 1); db.setRoot("root", value); return value; } }
ObjectStore has a deadlock detection facility that breaks deadlocks, when detected, by aborting one of the transactions involved in the deadlock. By aborting one transaction (the victim), ObjectStore causes its locks to be released so other processes can proceed.
ObjectStore throws DeadlockException when it detects a deadlock. This causes ObjectStore to abort the transaction that causes the deadlock. You can change which transaction ObjectStore aborts by changing the setting of the Deadlock Victim Server parameter.
ObjectStore does not detect deadlocks when the deadlock is distributed across multiple Servers.
Determining Transaction Boundaries
When determining whether or not to commit a transaction, consider database state, whether or not to combine transactions, and interdependencies among cooperating threads. Inconsistent Database State
You should not commit a transaction if the database is in a logically inconsistent state. A database is considered to be in an inconsistent state if at that moment a just-started transaction would encounter problems upon viewing the current state of the data. Combining Transactions
The transaction commit operation requires network interaction with the ObjectStore Server. Consequently, you might be able to improve performance by combining several logical transactions into a single transaction. Multiple Cooperating Threads
If your application uses cooperating threads, you must take this into account when determining when to commit transactions. For example, you do not want to create a situation where one thread commits a transaction while a cooperating thread is updating persistent objects. The commit() method might make all persistent objects stale for all cooperating threads. If the commit() method retains persistent objects, ObjectStore discards any modifications to retained persistent objects at the start of the next transaction. You must coordinate the Transaction.begin() and Transaction.commit() operations among cooperating threads.
Synchronizing threads is like having a joint checking account. Suppose the amount in the checking account is $100.00. Your partner writes a check for $50.00. Then you try to cash a check for $75.00. This does not work. It does not matter that it was your partner and not you who wrote the check for $50.00. You and your partner have to cooperate.
Performance Considerations
Committing a transaction, even a read-only transaction, has a certain amount of overhead associated with it. If you have a lot of small transactions. You might want to consider combining some of them into larger transactions.
Updated: 10/07/98 08:45:35