A hallmark of any well-written, reliable enterprise application is the proper usage of transactions. An application that only reads can get away with moderate laxness in transaction usage, and an application that only touches one place for data can get away with a smaller bit of laxness. But a typical enterprise application that reads messages from a queue, processes them, and writes to a data store must handle transactions properly or else it will be more of a random data generator than a reliable enterprise-level piece of software.
This does not mean that every enterprise application must use the stringent controls of full XA transactions, although every application developer should know how to implement them. It does mean, however, that you need to understand how transactions work and the techniques for using transactions in a way that makes your applications reliable. If you choose to leverage the powerful features of IBM WebSphere eXtreme Scale in your enterprise application, you need to know how to integrate it into whatever kinds of transactions you decide to use.
A transaction primer
What exactly is meant by "transaction"? A transaction is nothing more or less than a way to coordinate reads from (and writes to) one or more places that hold data.
An essential feature of a transaction is to be atomic, meaning "all or nothing"; if your application writes several bits of data as part of a single logical operation, you want to ensure that either all bits are written or none of them are written. For example, if you transfer $1,000 from your savings account to your checking account, you want to be certain that the money taken from savings does in fact end up in checking. At worst, you want to find out that your savings and checking are unchanged and that you have to redo the transfer; you never want to discover that the money was deleted from your savings but never made it into your checking. This core idea that a transaction is atomic and that the data involved is always kept consistent is key to the discussions which follow. It's all about HOW you ensure this happens.
The read part of transaction handling is relatively simple in that you only need to decide if you (as most do) want to read only data that was written as part of prior transactions, or if you want your reading to include bits that were changed as part of a transaction which might not yet be complete. This latter is called dirty read and is useful in certain circumstances. The former type, called read committed, deals with more complex considerations of transaction usage, and will be the focus of the remainder of this article.
In your application logic, you have to assert what group of data writes should be all done or not done together. You do this using a transaction's scope. Application logic can begin a transaction, commit a transaction, and rollback a transaction. All reads and writes done after a transaction begins and before it is committed or rolled back are considered within the scope of that transaction. Some environments where your application can run might support nested transactions, but for the purpose of this article, let's simplify and assume any thread of execution can have at most one active transaction. So, the pattern for a simple application is:
- Begin a transaction.
- Read some data.
- Perform some logic.
- Write some data.
- Commit the transaction.
Your application might explicitly call system-standard methods that do the begin, commit, and rollback, just as you call methods to read and write data. However, application environments like Java™ EE also enable you to implicitly perform these steps, mainly so you don't forget to do them or do them improperly. For example, EJB session bean methods can automatically begin a transaction when they are called (between the time when some code calls the EJB method and when the first line of the method executes); this transaction automatically commits when the EJB method returns to the caller, and automatically rolls back when any exception is thrown from the EJB method.
Regardless of whether you explicitly begin/commit/rollback your transactions (also known as user transactions or bean-managed transactions) or use implicit transactions (also known as container-managed transactions), everything you will read in this article henceforward works the same. The point is this: just because you didn't write any code to use transactions doesn't mean you aren't using transactions.
Transactions can be local, meaning you are only touching a single data source within the transaction; this greatly simplifies commit and rollback handling. Transactions can be global, meaning you are touching more than one data source. If some of these data sources are running in remote processes, then you have a distributed transaction.
One-phase commit and two-phase commit
Related to local, global, and distributed are the terms in the title of this section. These terms are shorthand for the technical details of how the transactions are managed and will be used from here on in. But before discussing these terms, we need to cover one other topic.
References have been made to transactions as being "managed" but not to who does the managing. This is actually the heart of this article. It doesn't happen by magic. Every transaction is managed by some chunk of code in your infrastructure which, by its nature, must be a chunk of software that is used by everything involved in a transaction. Java EE has a transaction manager known as Java Transaction API or JTA. It is the transaction manager that implements the begin, commit, and rollback methods.
To use an analogy, the transaction manager is the conductor of your symphony of transactions. The musicians are the resource managers; IBM WebSphere MQ, all enterprise-quality databases, WebSphere eXtreme Scale, and many other infrastructure products are written to be resource managers for Java EE transactions. A resource manager must include support for a given transaction manager before it can play in that "orchestra." It must have methods the transaction manager can call as needed.
A one-phase transaction or 1PC is the simplest kind of transaction. When a transaction begins, the transaction manager notifies all resources (queues, databases, and so on) that are to be involved that a transaction with a unique ID has begun. Prior configuration (that is, a deployment descriptor) is how the transaction manager knows what resource managers might potentially be involved. As the application logic reads and writes data to the various resources, it is up to these resources to give and take data in a way that can be later committed or rolled back.
Let's say, for example, there is only one resource, a database. You have written some data to the database within this transaction. As you are executing some subsequent logic, you hit an error and cannot continue. This causes the transaction to roll back. The transaction manager notifies all resources to roll back any writes; the result must look as though the writes had never happened. Thus ends the transaction, no change has happened anywhere, and the world is just as it was before the transaction began.
Now, suppose you didn't hit any error and "commit" is called for this transaction. The transaction manager notifies all resources to commit any writes done to them. Here's where it gets interesting: this data writing might work or it might fail (for example, what if the database server dies?). In this example of only one resource, this resource failing on commit is not a horrible problem – again, the world is as it was before the transaction began and you can retry the transaction once the database is fixed. 1PC works very well for "local" transactions; in fact, the term "1PC" is often used interchangeably with "local." But what if you have two resources and the first one commits fine but the second dies?
A two-phase transaction or 2PC (also called XA) handles this situation. The transaction manager's "begin" behavior is pretty much the same for 2PC as for 1PC; it notifies all potential resources that the transaction has begun. Rollback behavior is also pretty much the same, as all the participating resources can roll back their own writes (if they cannot, they cannot claim to be able to participate in either 1PC or 2PC transactions).
Commit behavior is where things are different. When "commit" is called, the transaction manager calls each participating resource twice, in two phases. In the first phase, it tells each participant in turn to prepare to commit; in the second phase it tells each participant in turn to actually commit its writes. In the prepare phase, the participant does whatever it must so that the later commit call is guaranteed to succeed — but WITHOUT actually committing the data so that the writes can be "undone" (this usually involves major code, but for now just trust it can be done). A participant is permitted to fail during prepare but not during commit. With this contract met by everyone, you can see that any number of 2PC participants can work. If anyone has a problem during prepare, the transaction manager can call all participants that previously succeeded in their prepare and tell them to roll back; in this way the entire transaction is rolled back. If everyone succeeds in prepare, then by definition everyone will succeed in commit and the entire transaction commits successfully.
If 2PC transactions are so great, why doesn't everyone use them? 2PC transactions have a reputation for being significantly slower than 1PC transactions. This might be true depending on the particular application, the infrastructure it runs on, and the load on the application. Since the vast majority of transactions commit successfully, the slowness that is due to the extra two-phase commit logic is rarely needed; if there were a way to handle failed transactions that was slower but only executed where there was in fact a failure, that might be better for overall thruput. For this reason, developers have come up with techniques to demonstrate how 1PC can be used with multiple participating resources.
In a nutshell, compensation logic is logic that an application adds to itself to handle the "undoing" of previously committed writes in the event that some participant (other than the first one) fails on commit. This can take a few general forms:
- Tolerant, meaning it doesn't really matter if some changes are written and others are not. Perhaps the written data won't actually be re-read and used until the final participant writes its data (when the transaction is redone), or until an explicit "done" flag is written by your application after all other data is confirmed committed (which, again, might take a redo for some bits).
- True compensating, meaning the application actually writes logic to check and undo any participants that committed prior to some participant failing. This logic would typically only execute if the code beginning the transaction caught an exception indicating that the transaction rolled back; other than this catch, there is no overhead for successful commits.
As you can imagine, writing true compensating logic can sometimes be very complex, even moreso the more resources that are participating. This might drive you back to 2PC and JTA where this is all handled automatically.
WebSphere eXtreme Scale and transactions
There are three things you need to know at this point about WebSphere eXtreme Scale:
- The first thing is that it is a 1PC resource only; it cannot participate in a 2PC transaction as a full participant (don't stop reading now, it can still participate in 2PCs). This is because there is currently no WebSphere eXtreme Scale API that lets you do everything you need for committing data to the grid without actually committing it; that is, there are no APIs that enable you to meet the JTA contract for the "prepare" method. All WebSphere eXtreme Scale has is "commit."
- The second thing you should know is that WebSphere eXtreme Scale has its own transaction manager, but it only understands how to manage 1PC transactions.
- Third, you should know that WebSphere eXtreme Scale can be happily made to participate with certain other transaction managers, such as WebSphere Application Server's JTA.
There are two main transaction-related patterns you can use when including WebSphere eXtreme Scale in your applications. The patterns are defined by which transaction manager you choose to use, either the WebSphere eXtreme Scale transaction manager or the WebSphere Application Server JTA transaction manager; the latter is mentioned specifically because it includes an extension to JTA that is critical to WebSphere eXtreme Scale (or any 1PC resource) participating in a 2PC transaction. You'll learn about this extension and why it is critical as you continue in this article. The next sections explain the pros and cons associated with these two patterns.
Transaction Pattern 1 (WebSphere eXtreme Scale transaction manager)
Using WebSphere eXtreme Scale TransactionCallback to coordinate one additional one-phase resource (besides WebSphere eXtreme Scale)
Create, read, update, and delete operations in a WebSphere eXtreme Scale map always happen in the context of a WebSphere eXtreme Scale transaction. Two very common general caching patterns for using WebSphere eXtreme Scale are the side cache pattern and the write-through cache pattern:
- In the side cache pattern, your WebSphere eXtreme Scale client code will get data from the database if it's not yet in the WebSphere eXtreme Scale cache and will write data not just to the cache but also to the database.
- In the write-through pattern, the grid is configured to use a Loader plug-in. The Loader plug-in is responsible for fetching data from a back end database to put into the WebSphere eXtreme Scale cache, or for writing the changes that occur in a cache into the back end database.
The back end database changes also must happen in the context of a transaction. It is this need to coordinate the WebSphere eXtreme Scale transaction and some other transaction that requires you to do something special, in one way or another.
This transaction pattern assumes you chose to use the WebSphere eXtreme Scale transaction manager, which means you will call the WebSphere eXtreme Scale Session.begin(), commit(), and rollback() methods in your code. Be aware that the Java EE EJB session bean mechanism won't work with the WebSphere eXtreme Scale transaction manager; you cannot make the WebSphere eXtreme Scale transaction manager automatically begin a transaction when you enter a session bean method or commit it when you exit. If you want that, you must go with JTA.
If your application has written to any other transactional resource besides WebSphere eXtreme Scale, such as a writing to a database (side cache or write-through cache) or reading a message from WebSphere MQ (which deletes the message you read during commit), then you will want to write a TransactionCallback plug-in; this is how you coordinate a WebSphere eXtreme Scale transaction and some other transaction. Notice that I said "database or WebSphere MQ"; this pattern can only deal with WebSphere eXtreme Scale and one other resource because the WebSphere eXtreme Scale transaction manager can only deal with one-phase transaction processing (1PC). In other words, it understands how to call resources for "commit" but doesn't have any idea about "prepare."
To be very clear, the only time you need to write a WebSphere eXtreme Scale TransactionCallback is if you have chosen to use the WebSphere eXtreme Scale transaction manager. Only the WebSphere eXtreme Scale transaction manager calls a TransactionCallback implementation. JTA and other transaction managers are unaware of this and any other WebSphere eXtreme Scale plug-ins. Also, you very likely will want to write your own TransactionCallback and not use the WebSphereTransactionCallback or JPATxCallback shipped with WebSphere eXtreme Scale; the former doesn't really give you transactionality (read what its javadoc says about it being a "volatile participant") and the latter is really part of the implementation for the built-in JPA loaders and not intended for any other use (the only reason it is not a private class is you must be able to wire it into your XML when you wire in the JPA loaders).
A TransactionCallback plug-in coordinates the WebSphere eXtreme Scale transactions with the transactions of the other resource. A TransactionCallback plug-in participates in the begin and commit (and rollback) transaction lifecycle events. WebSphere eXtreme Scale does not support two-phase commit transactions (XA).
A TransactionCallback plug-in can be used on the client and on the container servers where your Loader plug-ins run. On the client, it is called as part of the client-side WebSphere eXtreme Scale transaction processing (begin, commit, rollback, and so on). Let's talk first about what happens during a single transaction involving the side-cache pattern and what you need to do in your TransactionCallback methods (you can see this in the included sample code MyTransactionCallback.java):
- When your grid is initialized (typically at startup), your TransactionCallback.initialize(ObjectGrid og) method is called. In this method, initialize anything you need to for your resource (you might not need to do anything). Also call og.reserveSlot(TxID.SLOT_NAME) and save the returned int from the TransactionCallback in a variable called, for example, "int dbSlotNum." This slot is a way for you to pass data (such as a database connection object) from your TransactionCallback to other code, should you need to.
- During normal execution of your application, your client code calls Session.begin():
- The WebSphere eXtreme Scale transaction manager first calls other code so that WebSphere eXtreme Scale itself begins to participate in the transaction as a resource.
- Next, WebSphere eXtreme Scale transaction manager calls your TransactionCallback.begin(TxID id) method so that your other resource can begin participating. In this method, you should get whatever you need to begin a transaction for the other resource you will use. For a database accessed with JDBC, you typically get a Connection. Store the Connection object in the TxID via id.putSlot(dbSlotNum, connection). By the time you exit your begin method, a separate transaction for your resource should have begun.
- Within the scope of this WebSphere eXtreme Scale transaction, your client code performs business logic, you get and put to the grid, and so on. When it comes time to interact with your other resource, you will need the Connection (or other object) you got in your begin(...) method. How do you get it from within your client code? Session.getTxID().getSlot(int slotNum) will return it from the same TxID object in which you stored it. How do you get the slotNum value? You stored it in your TransactionCallback above as dbSlotNum, and you can get a reference to your TransactionCallback via ObjectGrid.getTransactionCallback(). You can't just get a Connection in your client code directly because your TransactionCallback needs that Connection so that it can close it (to commit the transaction or roll back the transaction) when the WebSphere eXtreme Scale TransactionManager tells it to.
- If an unrecoverable error happens in your client code during your logic, your client code will call Session.rollback().
- This time, the WebSphere eXtreme Scale transaction Manager first calls your TransactionCallback's rollback() method. In this method, you should do whatever you need to roll back the other resource's transaction. Use TxID.getSlot(slotNum) to get your Connection or other object. For a JDBC Connection you can call its rollback() method. Don't forget to close the connection and other objects associated with it, just as you always (should) do.
- The WebSphere eXtreme Scale transaction manager then calls other WebSphere eXtreme Scale code to roll back any grid writes you may have done.
- If no unrecoverable error happened, then your client code will call Session.commit() when it is completed.
- The WebSphere eXtreme Scale transaction manager will first call your TransactionCallback's commit(TxID) method. In this method, you should do whatever you need to commit the other resource's transaction. Use TxID.getSlot(slotNum) to get your Connection or other object. For a JDBC Connection, you can call its commit() method. Again, be sure to close the Connection and any other related objects as usual. If for any reason your attempt to commit the other resource's transaction fails, you can throw an exception and WebSphere eXtreme Scale will roll back its grid transaction; everything will remain consistent, as it was before this transaction began.
- The transaction manager will now tell WebSphere eXtreme Scale to commit its grid changes for this transaction. At this point, you might be thinking that if WebSphere eXtreme Scale fails to commit its changes, it won't be consistent with the other resource. That would be true, except for one key fact about WebSphere eXtreme Scale: the grid stores data in memory and has built-in high availability via replicas. The only way that WebSphere eXtreme Scale could fail to commit its changes is if the grid is totally down when your client tries to commit. If that occurs, the only record of your change is the other resource (for example, your backend database). When the grid comes back up, it will reload itself from your back end and will then be consistent. This is why this same transaction pattern will not work if you are not actually using the side cache pattern, but instead are writing some other data unrelated to the grid to a database as part of this transaction.
We have discussed what happens on the client (for example, for the side cache pattern). Now, let's discuss what happens if you use the write-through pattern. As for the 7 steps above, you still perform Session.begin(), commit() and rollback() in your client code. The main difference is that your other resource logic (for example, JDBC logic) happens on the container, in your loader (see the sample MyLoader.java). On the container, your TransactionCallback is called similarly as part of the container transaction, which is commonly begun and committed in coordination with the client transaction’s commit phase; client commit is also when Loader.batchUpdate(...) is called. In the case of a loader configured for write-behind, the container transaction is begun and committed periodically based on your write-behind configuration. On the container side, your TransactionCallback methods do pretty much the same thing (which is why the same TransactionCallback can be run both on the client and on the container). Notice that, in the write-through pattern, the loader must obtain access to the other resource (for example, JDBC Connection) from your TransactionCallback (via the TxID object) just as your client does in step 3, above, when you use the side cache pattern. The sample loader has a bit of code in its preloadMap(...) method to save away a reference to the local container-side TransactionCallback instance, enabling the loader to call it as needed in get(...) and batchUpdate(...).
One curious point is that, on the container side, your TransactionCallback begin() method will not be called as reliably as it is on the client side. This is usually not a serious issue; to handle this difference between client and container, use the same logic to get the necessary state (for example, database connection) and begin your back end system transaction as a singleton operation within the Loader get() and batchUpdate() methods. The term "singleton" here means to use "if" logic to insure that you only begin the back end transaction once, even if your Loader.get() or batchUpdate() is called several times during one WebSphere eXtreme Scale transaction. (Loader.get(...) and batchUpdate(...) input parameters include the TxID you will need. You can call TxID.getSlot(slotNum) first to see if you have already obtained a connection (or whatever) for this transaction; if null is returned from getSlot, you should obtain a connection and then store it with TxId.putSlot(slotNum, connection). In the sample code included with this article, this singleton logic is part of MyTransactionCallback.insureConnection(...), which MyLoader calls.)
For more information on how to write and, especially, configure in a TransactionCallback, see the Redbook WebSphere eXtreme Scale Best Practices for Operation and Management and the product documentation.
Pattern 1 detail: How to handle WebSphere eXtreme Scale agents and their transactions
If you use the WebSphere eXtreme Scale DataGrid APIs (agents), there are additional considerations on transaction usage. When you execute an agent, one instance of the agent code runs for each partition for that map (or for those for which you supplied keys). Each instance runs in a separate thread related to that partition and thus runs in its own transaction; WebSphere eXtreme Scale begins the transaction before your agent code is called and commits the transaction after you return. If you throw an exception in your agent code, WebSphere eXtreme Scale will roll back the transaction and propagate the exception back to the client.
With n separate transactions, there is the chance that some will succeed and some will fail. The DataGrid API gives you ways to know which transactions succeeded (committed at the container) and which failed (rolled back at the container). You can also encode this results state yourself in the optional object that you can return. However your client becomes aware of it, it is up to you to perform compensation logic if your requirements dictate it.
WebSphere eXtreme Scale commit when you have a synchronous replica
The description above of Pattern 1 (and also Pattern 2, below) glossed over one aspect of WebSphere eXtreme Scale commit processing. It is extremely common (near universal outside of session caching) that WebSphere eXtreme Scale users have one synchronous replica; it is called synchronous because data is committed to both the WebSphere eXtreme Scale primary shard and the replica shard together. However, transaction management is all about the details, so what exactly does WebSphere eXtreme Scale do?
In short, WebSphere eXtreme Scale does something sort of like 2PC for its primary and synchronous replica: on being called by a transaction manager (whether the WebSphere eXtreme Scale one or JTA) to commit, WebSphere eXtreme Scale will:
- Apply changes to primary shard.
- Apply changes to replica shard.
- Complete the commit to the primary.
- Complete the commit to the replica.
- Return to the transaction manager.
WebSphere eXtreme Scale doesn't return to the transaction manager in between these calls. It is all done as part of what the transaction manager sees as a 1PC commit.
A failure can happen in — or in between — any of these steps. Here's the behavior:
- A failure before step 1, in step 1, between steps 1 and 2, or in step 2 causes the whole WebSphere eXtreme Scale transaction to rollback. The client application sees an exception and should retry the transaction.
- A failure between steps 2 and 3, or in step 3 can only happen if the primary container JVM dies. In this case, the replica is promoted to become the primary. A replica will auto-commit changes that were applied to it when it is promoted, so the new primary contains the committed data. However, an exception is still thrown back to the client – an exception indistinguishable from failure scenario a. You must design your application to be able to retry and not mind if the WebSphere eXtreme Scale portion of the change is present and you are simply writing it again.
- A failure between steps 3 and 4 can only happen if the replica container JVM dies. In this case, the primary contains the committed data; when a new replica is created, its content comes from the primary and it will contain the committed data.
- A failure between steps 4 and 5 means the primary and replica both contain the committed data. The client application will see an exception and, as in scenario b, its grid writing must be indempotent.
The reason this is considered "sort of like 2PC" is because in WebSphere eXtreme Scale the commit is done as a "fire and forget" message. This greatly speeds up WebSphere eXtreme Scale but does introduce a window of danger if and only if bandwidth is short. With bandwidth stress, it is theoretically possible for the client application to receive the "commit successful" response before the replica has received and acted on the commit. This is another reason to size your WebSphere eXtreme Scale environment with sufficient bandwidth, as well as memory and CPU. Under all but the most stressed situations, this error window is vanishingly small. I have never observed it and have not heard of anyone observing it.
WebSphere Application Server transaction manager and last participant support
The critical WebSphere Application Server feature mentioned earlier that enables WebSphere eXtreme Scale to participate in two-phase transactions is called last participant support. It's an enhancement to how 2PC commit works (begin and rollback processing are not affected).
Here's how it works. Suppose you have a WebSphere MQ queue, a JDBC database, and a WebSphere eXtreme Scale grid. WebSphere MQ and the database are both fully 2PC, while WebSphere eXtreme Scale is only 1PC. In a 2PC transaction, WebSphere MQ, the database, and WebSphere eXtreme Scale are enlisted to participate in the transaction. When the transaction is committed, the following happens:
- Prepare phase
- WebSphere MQ is called to prepare.
- The database is called to prepare. (These first two steps can happen in any sequence.)
- The WebSphere Application Server JTA transaction manager recognizes that WebSphere eXtreme Scale is only 1PC (because WebSphere eXtreme Scale is configured as a one-phase capable (local) transaction resource in the application's deployment descriptor). The JTA transaction manager can permit, at most, one 1PC resource in a 2PC transaction (you'll see why shortly). Knowing that it is the one and only 1PC resource, the JTA transaction manager ensures that WebSphere eXtreme Scale is always the last resource it calls during the prepare phase. Furthermore, because it can't call a 2PC "prepare" method, it calls a 1PC commit method instead. This is the heart of last participant support. The "last participant" is permitted to throw an exception from its 1PC commit method and it is treated just as if one of the 2PC components threw from its prepare step; the entire transaction is rolled back. Assuming WebSphere eXtreme Scale doesn't have any errors, you know for certain that the entire transaction will commit because there is nothing left to do but the commit phase, which by contract cannot have any failures.
- Commit phase
- WebSphere eXtreme Scale (or other last participant) is skipped here.
- WebSphere MQ is called to commit.
- The database is called to commit.
As of WebSphere Application Server V7.0, you can also control the order of the regular participants in a transaction. This does not mean that you can make the last participant anything other than last; all you can do is affect the 2PC participants. One viable scenario for doing this is if you plan to write some compensation logic, and it would be simpler if one resource commits first versus another. Details on this are beyond the scope of this article but you can read about it in the WebSphere Application Server Information Center.
Transactional Pattern 2 (JTA transaction manager, or XA)
Integrate WebSphere eXtreme Scale into an XA Transaction Using WebSphere eXtreme Scale resource adapter
So far, we've discussed in general terms how a resource participates in a JTA transaction; about the JTA transaction manager calling a resource, but not about how this is done. If JTA is an architecture that enables any resource to participate, there must be some standard API that they both understand. That API is the Java EE Connector Architecture (JCA).
JCA defines both how a resource wires itself into a Java EE application server and application (usually using deployment descriptor XML) and the API that a participant must implement. The API within JCA is called a resource adapter (RA). You are already familiar with the JDBC RA for your database; it is involved when you look up a JDBC data source and get a JDBC connection. WebSphere Application Server's JMS and WebSphere MQ also have RAs of their own. When I began my work in this area, there was no WebSphere eXtreme Scale RA as part of the WebSphere eXtreme Scale product so I had to write one. The just-announced WebSphere eXtreme Scale V8.5 includes a JCA resource adapter that supports JTA (XA) transactions as a last participant. Although this is now a feature of the product, it is still useful to understand how this feature works internally.
An RA can be written for either a 1PC or a 2PC resource. Typically, a 2PC RA is written to also work if called in a 1PC style; a smart transaction manager will do that whenever it can (for example, if only this resource is involved in a transaction) because it usually runs faster.
The JCA is an open spec, which means you don't have to be the developer of a resource to write an RA for it, provided the resource has the public APIs you need to fulfull the contract required in the API.
An RA must implement these methods for a 1PC resource (embodied in the javax.resource.cci.LocalTransaction interface):
public void begin() throws ResourceException
public void commit() throws ResourceException
public void rollback() throws ResourceException
A 2PC resource must implement the following methods (embodied in the javax.transaction.xa.XAResource interface); there are more but these are the key ones:
public void start(Xid xid, int flags) throws XAException
public int prepare(Xid xid) throws XAException
public void commit(Xid xid, boolean onePhase) throws XAException
public void end(Xid xid, int flags) throws XAException
public void rollback(Xid xid) throws XAException
The start(...) method is the XA equivalent of begin(). The end(...) method is included to enable a resource to separate cleanup from the actual commit. The other methods not mentioned here enable more subtle edge-case situations to be handled in a standardized way.
Since WebSphere eXtreme Scale is only capable of 1PC transactions (WebSphere eXtreme Scale has no session.prepare() method to call), the WebSphere eXtreme Scale RA is written as a 1PC capable resource adapter by implementing the LocalResource interface, along with a number of other JCA interfaces needed to make the whole thing work in WebSphere Application Server.
Pattern 2 detail: What the client application does
An application that wants to use WebSphere eXtreme Scale (and the WebSphere eXtreme Scale RA) together with, for example, JDBC and WebSphere MQ in a 2PC transaction will use JNDI to look up the WebSphere eXtreme Scale RA's ConnectionFactory. It does this within the scope of a JTA transaction, meaning that either within the code of an EJB session bean method (where, on entry, a transaction is already begun) or after, the app has itself begun the JTA transaction as a "user transaction." This is similar to how an application would get a JDBC ConnectionFactory.
Listing 1 shows part of the sample code for using the WebSphere eXtreme Scale RA in an EJB session bean method.
// Declare with your other bean instance variables XSConnection con; // This bit is typically in an EJB init method or the XSConnectionFactory // (and even the XSConnection) may be passed to the EJB on creation InitialContext ctx = new InitialContext(); XSConnectionFactory cf = (XSConnectionFactory) ctx.lookup("java:comp/env/wxsconnection"); con = cf.getConnection("MyGrid"); ... // This bit is typically within an EJB method. At this point a transaction must have // been started. You will need the XS Session object so you can interact with // your maps under the scope of the current active transaction. Session ogSession = con.getSession(); ObjectMap myMap = session.getMap("MyMap"); ... // When you have completed all map writes for this transaction, close the connection // on your way out. Close does NOT commit the transaction, if this is a local // transaction that you explicitly called tran.begin() on then you must call // tran.commit() explicitly also. If you are using container-managed transaction // then the return from your EJB method will commit the transaction. con.close();
Pattern 2 detail: What the JTA transaction manager and WebSphere eXtreme Scale RA does
Here is what happens with the WebSphere eXtreme Scale RA when someone calls an EJB (or MDB) method that involves, for example, a WebSphere MQ queue, a database via JDBC, and a WebSphere eXtreme Scale grid:
- WebSphere Application Server begins a 2PC transaction (if you are doing container-managed transactions; otherwise, you begin the transaction.) WebSphere Application Server finds all participants; for example, WebSphere MQ (2PC, meaning a 2-phase-capable resource), JDBC (2PC) and WebSphere eXtreme Scale (1PC and thus the "last participant" for this XA transaction).
- When your application first touches the MQ queue, WebSphere Application Server calls the MQ RA's begin method (actually, it is called "start"); actual order is undefined, by the way.
- When your application first touches the database, WebSphere Application Server calls the JDBC RA's begin method (also called "start").
- When your application first touches the WebSphere eXtreme Scale grid, WebSphere Application Server calls the WebSphere eXtreme Scale RA's begin method. In here, the WebSphere eXtreme Scale RA gets a WebSphere eXtreme Scale session and internally calls Session.begin(). The WebSphere eXtreme Scale RA also saves the Session object "in" this transaction for later.
- In your method, you interact with WebSphere MQ, with JDBC, and with WebSphere eXtreme Scale maps obtained from your XSRA connection.
- You return from the last line of your method (you should have closed all your connections by now).
- WebSphere Application Server commits the XA transaction:
- WebSphere Application Server calls the MQ RA's prepare method. WebSphere MQ does what it needs and can throw exceptions if anything goes wrong; actual order is undefined
- WebSphere Application Server calls the JDBC RA's prepare method. JDBC does what it needs and can throw exceptions if anything goes wrong.
- Since the WebSphere eXtreme Scale RA is only 1PC, WebSphere Application Server does not attempt to call prepare on it.
- As the last participant, WebSphere Application Server calls the WebSphere eXtreme Scale RA's commit method first. The WebSphere eXtreme Scale RA uses the saved Session object and calls session.commit(). If there is a problem and it throws an exception, the transaction is rolled back (which is the same thing that happens if any prepare call fails with an exception).
- WebSphere Application Server calls the MQ RA's commit method. Per the 2PC contract, WebSphere MQ is not permitted to throw an exception here, ever. Actual order is undefined.
- WebSphere Application Server calls the JDBC RA's commit method. Per the 2PC contract, JDBC is not permitted to throw an exception here, ever.
- Control returns to the caller of the original EJB (or MDB) method.
Notice that nowhere in this processing is there a need for an XS TransactionCallback. Actually, an XS TransactionCallback cannot help here at all — it can only cause errors after the final commit call in "post-commit" processing. If you were to use a WebSphereTransactionCallback together with a JTA transaction and the WebSphere eXtreme Scale RA, you will see a NoActiveTransaction exception. When the WebSphere eXtreme Scale RA commit calls Session.commit(), the TransactionCallback is called by the WebSphere eXtreme Scale runtime and tries to commit the session — not knowing it was already committed by the WebSphere eXtreme Scale RA (when JTA told it to do so). Hence, the NoActiveTransaction exception.
It is indeed possible to integrate IBM WebSphere eXtreme Scale with other middleware in a transactional way – but you have to know how and you have to know the limitations. You can combine WebSphere eXtreme Scale with one other resource even if you are not in a full Java EE environment, such as WebSphere Application Server. If you are in a Java EE environment, so much the better because you can use JTA, but you are still limited to WebSphere eXtreme Scale and one other resource. If you are in the WebSphere Application Server environment in particular, you can combine WebSphere eXtreme Scale with any number of 2PC resources by using the WebSphere Application Server JTA and its special "last participant" support, but you need the new WebSphere eXtreme Scale resource adapter to do it. If you find using JTA slows performance too much, consider using compensation logic and see if the added speed is worth the extra coding effort. Whichever technique you choose, you will (if you do it right) end up with a reliable, enterprise-quality transactional application.
I would like to thank my WebSphere eXtreme Scale consulting colleagues and clients with whom I spent many pleasant arguments on this topic. Special thanks go to the IBM GBS team for whom I first wrote a WebSphere eXtreme Scale resource adapter (before it was added to the product), and especially Khalid Asad. My greatest thanks, however, go to the WebSphere eXtreme Scale development team and, in particular, Jared Anderson, who allowed me an "all access" pass into the inner workings of WebSphere eXtreme Scale, and Chris Johnson, who went beyond my original WebSphere eXtreme Scale RA and created a rich and wonderful product feature. Thanks guys.
|Code sample||1205_jolin_attachment.zip||5 KB|
- WebSphere Application Server Information Center
- WebSphere eXtreme Scale V8.5 Information Center
- WebSphere Application Server product information
- WebSphere eXtreme Scale product information
- IBM developerWorks WebSphere