We will try to understand the behavior of SOA transaction boundaries by creating simple BPEL process and gradually adding functionality to it.
At the very basic, every BPEL process executes in the context of a transaction. It either creates a new transaction or joins an existing transaction.
Let us a create BPEL process which consumes message from a JMS Queue.
Oracle SOA 220.127.116.11.8 on Windows 7 64 bit OS.
CREATE TABLE SOURCETBL(SEQ NUMBER NOT NULL,NAME VARCHAR2(20 BYTE),FLAG VARCHAR2(20 BYTE) NOT NULL,EMAIL VARCHAR2(200 BYTE),CONSTRAINT SOURCETBL_PK PRIMARY KEY(SEQ)ENABLE); CREATE TABLE TARGETTBL(SEQ NUMBER NOT NULL,NAME VARCHAR2(20 BYTE),FLAG VARCHAR2(20 BYTE) NOT NULL,EMAIL VARCHAR2(200 BYTE),CONSTRAINT TARGETTBL_PK PRIMARY KEY(SEQ)ENABLE)
DataSources & JNDI Names
|Name||Datasource JNDI||DBAdapter JNDI|
Following JMS resources are used in the examples.
|Queue||NonXA CF||XA CF||Adapter XA JNDI||Adapter Non XA JNDI|
Queue is configured with following “Delivery Failure” Options.
Redelivery Delay Override: 10000
Expiration Policy: Discard
<?xml version="1.0" encoding="windows-1252" ?> <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://xmlns.oracle.com/ex/emailrec" targetNamespace="http://xmlns.oracle.com/ex/emailrec" elementFormDefault="qualified"> <xsd:element name="ERecord" type="ERecordType"/> <xsd:complexType name="ERecordType"> <xsd:sequence> <xsd:element name="seq" type="xsd:integer" /> <xsd:element name="name" type="xsd:string" /> <xsd:element name="email" type="xsd:string" /> </xsd:sequence> </xsd:complexType> </xsd:schema>
BPEL Process with JMS Adapter
- Create a SOA Application.
- Add JMS Adapter to consume messages based on above schema. Use Non-XA JNDI to start with.
- Create a BPEL component based on wsdl created as part of above step and wire it with JMS Adapter.
- In the BPEL process just add an “Empty” activity.
Deploy the process. Add the following xml message to JMS Queue
<ERecord xmlns="http://xmlns.oracle.com/ex/emailrec"> <seq>1</seq> <name>Krishna</name> <email>firstname.lastname@example.org</email> </ERecord>
There is nothing interesting in this base process. A bpel instance would have been created and completed.
As part of the bpel flow, let us throw a Rollback fault. Let us see whether our message is rolled back to JMS queue.
Modify BPEL to have a rollback fault (after the empty activity) and deploy the process.
Add a message to JMS Queue.
Audit trail shows that the transaction was rolled back. However, if we see our JMS queue, our message is not rolled back to the queue.
Is this because we used non-xa?.
Using XA JNDI
Modify the JMS jca file to have XA JNDI instead of non-xa JNDI and redeploy the process. The result is same as before. Transaction would have rolled back , but we don’t see messages back in our queue.
Does it mean the rollback is not doing what it is supposed to do?.
Let us add one more action, inserting into DB table , as part of the BPEL flow.
- Add a DB Adapter to insert or merge into the table.
- use XA JNDI
- Add an invoke before “Throw” to call DB Insert.
- Add a assign/transform activity before invoke to initialize variable for DB insert.
Deploy the process and add a message to the queue. Audit trail will show that “DB Insert” was completed successfully and transaction was rolled back.
If we look at the table, there will be no record, which means “rollback” is doing what it is supposed to do. It roll backed the insert. However, the rollback didn’t restore the message back in JMS queue. So, we can conclude, DB Insert happens in a separate transaction and JMS dequeuing happens in a separate transaction.
The above results implies
- There will be two transactions.
- JMSAdapter transaction – which gets committed even before BPEL executes
This is due to the setting async.persist which will put the message in dlv_message table and commit the transaction. BPEL invoker threads will pickup from dlv_message and initiate BPEL, which will run in new transaction.
- BPEL flow – which runs in new transaction.
When rollback fault is thrown, only Transaction 2 is rolled back(no record in DB Table). However, transaction 1 will be committed. That is the reason, we don’t see the message back in the queue. So,if we use “aysnc.persist” for “oneWayDeliveryPolicy”, Bpel Engine will execute activities in two transactions as shown in above diagram.
If we want one transaction instead of two, we need to set “oneWayDeliveryPolicy” to sync.With this setting . DLV_Message table is not used. JMSAdapter and BPEL will get executed in the same thread.
Change “oneWayDeliveryPolicy” to “sync”. Redeploy the process and add a message to Queue.
This time, because of rollback fault, you can find that message is rolled back to JMS queue and retried.
Actually, with this setting also, it will be still two transactions as shown in below diagram.
The reason for above behavior is BPEL always starts new transaction. If there is any existing transaction, it suspends that one. Suspended transaction will resume after the current bpel flow completes.
JMSAdapter Transaction (Txn 1) will be suspended and BPEL starts a new transaction (Txn 2) to execute BPEL activities. When Rollback fault is thrown, Txn2 is rolled back. JMSAdapter gets a remote fault and since it doesn’t handle the fault, Txn 1 also will be rolled back. We can observe these faults in audit trail
To make BPEL to participate in existing transaction, instead of creating new one (which is default behaviour), we need to set “bpel.config.transaction” property to “required”.
If value is “required”, BPEL will participate in existing transaction , if any available. If not it will start a new transaction.
If value is “requiresNew” (default value), BPEL will create new transaction. If any existing transaction is available, that will be suspended.
Add “bpel.config.transaction” property to composite.xml with value “required” and deploy the process.
<component name="SoaTxBPEL" version="2.0"> <implementation.bpel src="SoaTxBPEL.bpel"/> <property name="bpel.config.transaction" type="xs:string" many="false">required</property> <property name="bpel.config.oneWayDeliveryPolicy" type="xs:string" many="false">sync</property> </component>
With this setting , Txn boundary will be like below and everything will get executed in single transaction.
When everything happens in a single transaction, and a rollback fault is thrown, we can not catch it inside bpel , even if we have a catch block.
Modify BPEL process to have a catch all and deploy it. In Audit trail we can see that though a “rollback” fault was thrown and catchall block exists, catchall branch doesn’t get executed. BPEL fault handling, as with other activities, happens in the context of transaction. When the existing only transaction is rolled back, BPEL will not be able to catch or handle faults.
In all these, we haven’t seen single record getting committed in the DB. If we want to commit the DB transaction and at the same time, we need to roll back messages in queue, we need to move DB Insert out of transaction . Use Non-XA JNDI for DB Insert. This will ensure DB Insert operation doesn’t participate in distributed transaction and gets committed irrespective of rollback fault.
As part of this post we saw how Transaction boundaries are determined when JMSAdapter and BPEL components are used and how values of bpel.config.oneWayDeliveryPolicy, bpel.config.transaction influences boundary demarcation. In next post (SOA Transaction Boundaries – Part 2), we will see how these values influence transaction boundaries when two bpel components are used.