This section contains guidelines and additional information on working with GemFire and its cache transactions.
Copy-on-read is not specifically a transaction setting, but if you are using transactions, you probably want copy-on-read behavior. To enable global copy-on-read for all reads, modify cache.xml or use the Java API.
<cache lock-lease="120" lock-timeout="60" search-timeout="300"copy-on-read="true">
Cache c = CacheFactory.getInstance(system) c.setCopyOnRead(true);The copy-on-read attribute and the operations affected by the attribute setting are discussed in detail in Managing Data Entries.
If you do not have copy-on-read set globally and are using replicated regions, you must explicitly make copies of the cache objects that you are modifying in your transactions. You can use CopyHelper.copy to make these copies.
CacheTransactionManager cTxMgr = cache.getCacheTransactionManager(); cTxMgr.begin(); Object o = (StringBuffer)r.get("stringBuf"); StringBuffer s = (StringBuffer) CopyHelper.copy(o); s.append("Changes unseen before commit. Read Committed."); r.put("stringBuf", s); cTxMgr.commit();
You can run a function inside a transaction and you can run a transaction inside a function, as long as your combination of functions and transactions does not result in nested transactions. You cannot call a function from inside a transaction if the function runs its own transaction.
You can also have multiple functions participate within a single transaction.
If you are suspending and resuming a transaction with multiple function calls, all function calls participating in the transaction must execute on the same member.
See Transactional Using a Function Example for an example.
Queries and indexes reflect the cache and ignore the changes made by ongoing transactions. If you do a query from inside a transaction, the query does not reflect the changes you made inside that transaction.
Collections and region entries used in a transaction must be created inside the transaction. After the transaction has completed, the application can no longer use any region entry or collection or associated iterator created within the transaction. If you try to use these, you get an IllegalStateException.
Region collection operations include Region.keySet, Region.entrySet, and Region.values. You can create instances of Region.Entry through the Region.getEntry operation or by looking at the contents of the result returned by a Region.entrySet operation.
Entry expiration and LRU eviction affect the committed state. They are not part of the transaction, and they cannot be rolled back.
LRU eviction operations do not cause write conflicts with existing transactions, despite destroying or invalidating entries. LRU eviction is deferred on entries modified by the transaction until the commit completes. Because anything touched by the transaction has had its LRU clock reset, eviction of those entries is not likely to happen immediately after the commit.
When a transaction commits its changes in a region with distributed scope, the operation can invoke eviction controllers in the remote caches, as well as in the local cache.
<expiration-attributes timeout="60" action="local-invalidate"/>
<expiration-attributes timeout="300" action="destroy"/>
A transaction that modifies a region having consistency checking enabled generates all necessary version information for region updates when the transaction commits.
If a transaction modifies a normal, preloaded or empty region, the transaction is first delegated to a GemFire member that holds a replicate for the region. This behavior is similar to the transactional behavior for partitioned regions, where the partitioned region transaction is forwarded to a member that hosts the primary for the partitioned region update.
The limitation for transactions with a normal, preloaded or empty region is that, when consistency checking is enabled, a transaction cannot perform a localDestroy or localInvalidate operation against the region. GemFire throws an UnsupportedOperationInTransactionException exception in such cases. An application should use a Destroy or Invalidate operation in place of a localDestroy or localInvalidate when consistency checks are enabled.
Pivotal GemFire APIs provide the ability to suspend and resume transactions. The ability to suspend and resume is useful when a thread must perform some operations that should not be part of the transaction before the transaction can complete.
When a transaction is suspended, it loses the transactional view of the cache. None of the previous operations (before calling suspend) are visible to the thread. Subsequently any operations that are performed by the thread do not participate in the suspended transaction.
When a transaction is resumed, the resuming thread assumes the transactional view. A transaction that is suspended on a member must be resumed on the same member. Before resuming a transaction, you may want to check if the transaction exists on the member and whether it is suspended.
You may optionally use the tryResume method.
If the member with the primary copy of the data crashes, the transactional view that applied to that data is lost. The secondary member for the data will not be able to resume any transactions suspended on the crashed member. You will need to take remedial steps to retry the transaction on a new primary copy of the data.
If a suspended transaction is not touched for a period of time, GemFire cleans it up automatically. By default, the timeout for a suspended transaction is 30 minutes and can be configured using the system property gemfire.suspendedtxTimeout. For example, to modify the default, you could set gemfire.suspendedtxTimeout=60 (time in minutes).
All standard Pivotal GemFire application plug-ins work with transactions. In addition, the transaction interface offers specialized plug-ins that support transactional operation.
GemFire has two types of transaction plug-ins-- Transaction Writers and Transaction Listeners. You can optionally install one transaction writer and one or more transaction listener per cache.
Like JTA global transactions, you can use transaction plug-in event handlers to coordinate GemFire cache transaction activity with an external data store. However, you typically use JTA global transactions when GemFire is running as a peer data store with your external data stores. Transaction writers and listeners are typically used when GemFire is acting as a front end cache to your backend database.
When you commit a transaction, if a transaction writer is installed in the cache where the data updates were performed, it is called. The writer can do whatever work you need, including aborting the transaction.
The transaction writer is the last place that an application can rollback a transaction. If the transaction writer throws any exception, the transaction is rolled back. For example, you might use a transaction writer to update a backend data source before the GemFire cache transaction completes the commit. If the backend data source update fails, the transaction writer implementation can throw a TransactionWriterException to veto the transaction.
A typical usage scenario would be to use the transaction writer to prepare the commit on the external database. Then in a transaction listener, you can apply the commit on the database.
When the transaction ends, its thread calls the transaction listener to perform the appropriate follow-up for successful commits, failed commits, or voluntary rollbacks. The transaction that caused the listener to be called no longer exists by the time the listener code executes.
Transaction listeners have access to the transactional view and thus are not affected by non-transactional update operations. TransactionListener methods cannot make transactional changes or cause a rollback. They can, however, start a new transaction. Multiple transactions on the same cache can cause concurrent invocation of TransactionListener methods, so implement methods that do the appropriate synchronization of the multiple threads for thread-safe operation.
A transaction listener can preserve the result of a transaction, perhaps to compare with other transactions, or for reference in case of a failed commit. When a commit fails and the transaction ends, the application cannot just retry the transaction, but must build up the data again. For most applications, the most efficient action is just to start a new transaction and go back through the application logic again.
The rollback and failed commit operations are local to the member where the transactional operations are run. When a successful commit writes to a distributed or partitioned region, however, the transaction results are distributed to other members the same as other updates. The transaction listener on the receiving members reflect the changes the transaction makes in that member, not the originating member. Any exceptions thrown by the transaction listener are caught by GemFire and logged.