Making sense of EJB3.x Transaction Attributes – Part 1 (REQUIRES_NEW)

For the previous past posts, I have dealth much with manual fine-grind transaction, mostly on the subject of Bean-Managed Transaction with javax.ejb.UserTransaction and I’ve talked about what were the differences between rollback() and setRollbackOnly(). Don’t be surprise that Container-Managed Transaction could achieve similar control over transaction if transaction demarcates are properly used. I am writting a series of linked-articles to address usage of @TransactionAttribute with TransactionAttributeType such as REQUIRES_NEW, MANDATORY, SUPPORTS, NOT_SUPPORTED and NEVER.

I’m not going to write about TransactionAttributeType.Required as it is common and by default all methods are at such and there ain’t much tricks to that. As for the rest of the demarcates, Oracle had documented on their transactional characteristics; but what many developers fail to understand (including me in the past) is how these demarcates behave in long units of transaction and where will be the appropriate implementation or their right usages. In this series, we are going to explore them one by one.

For the below example, I’ll illustrate the basic behavior of REQUIRES_NEW with 2 session beans by the name of ExampleSessionFacade1 and ExampleSessionFacade2 with their local interfaces as IExampleSessionFacade1Local and IExampleSessionFacade2Local for simplicity.

Understanding TransactionAttributeType.REQUIRES_NEW

Here at the below, I’ll show you code snippets instead of drawing sequence diagrams for better understanding. I’m not going to deal much with proper Exception handling for now, just keeping things simple.

@Stateless
public class ExampleSessionFacade1 implements IExampleSessionFacade1Local {
    @Resource
    private EJBContext context;

    @EJB
    private IExampleSessionFacade2Local exampleSF2;

    //Not necessary; just to make things clear
    @TransactionAttribute( TransactionAttributeType.REQUIRED )
    public void methodA() throws Exception {
        try {
            // Do something with the EntityManager...
            // and later invokes methodB() in exampleSF2

            exampleSF2.methodB();
        }
        catch(Exception ex) {
            if (!context.getRollbackOnly()) {
                /**
                 * This WILL NOT AFFECT the transaction in methodB().
                 * methods that you call like persist, merge or remove
                 * will still be carried out in methodB() and will be 
                 * updated in the database.
                **/
                context.setRollbackOnly();
            }
        }
    }
}

@Stateless
public class ExampleSessionFacade2 implements IExampleSessionFacade2Local {
    @TransactionAttribute( TransactionAttributeType.REQUIRES_NEW )
    public void methodB() throws Exception {
        //Do some other things with the EntityManager...
    }
}

Looking at the codes above, we have methodA() in ExampleSessionFacade1, it is annotated with TransactionAttributeType.REQUIRED. Within methodA(), it invokes methodB() through the session bean instance of ExampleSessionFacade2. methodB() is annotated as TransactionAttributeType.REQUIRES_NEW. Now, how the container works is this: the container will start a transaction for methodA(), let’s call it T1. As ExampleSessionFacade1’s methodA() invokes methodB() of ExampleSessionFacade2 and since methodB() was marked as REQUIRES_NEW, the container will then start a separate transaction, let’s call it T2 for methodB(). Both methods won’t share the same transaction together.

So that means, whether transaction T2 performs a commit (finishes properly at the end of the method and commits by the container to the database) or rollback (if context.setRollbackOnly() is called), that doesn’t affect what’s going on in transaction T1 as it is on a separate context. Like wise for transaction T1. If T1 had been marked for rollback ( invoke EJBContext’s setRollbackOnly() ), T2 will still finish the transaction with a commit (whatever you performed with the EntityManager will still be updated back to the database) and ignoring what’s happening to T1 even if T1 has a rollback.

Back to the above code example, if somewhere around methodA() throws an Exception and propagates the context.setRollbackOnly(), methodB() will still finish flawlessly (commits everything to the database) without being affected by the setRollbackOnly() by methodA().

Scenario for TransactionAttributeType.REQUIRES_NEW
to be In Action

TransactionAttributeType.REQUIRES_NEW is most suitable to be applied in the situation where the method’s transaction must not be affected by the transaction of the caller’s transaction.

One of the scenario that I could think of for methods to be annotated with REQUIRES_NEW to be in action is non-volatile audit trail (logging comments, remarks, actions by the user back into the database). Taking an example of a payment method to a 3rd party account from a banking module that deducts the amount of bank account when a transfer is successful (or nothing is performed is it fails), regardless if this transfer is successful or not (with a rollback), the logging facility would still need to be functional to log the status of the transaction and it must not be affect by the rollback of the transfer.

STICKY IMPORTANT HIGHLIGHT!!!:

** You can only bring out the effects of transaction attributes only when
you call the method through a session bean instance and NOT through a direct method call. Even if your methods are within the same bean, you need to get the local instance of the same bean and call through its local interface
instead of a direct method invoke.


This message will appear in all the posts within this series as a REMINDER.

Need an Applied Example?

I’m going to take one of the examples from my previous post. The database tables are the same, but instead of defining the session beans as Bean-Managed Transaction, it should be defaulted to Container-Managed transaction.

Here, I’ll show you a comprehensive use case where the TransactionAttributeType.REQUIRES_NEW can applied. This use case is about the transfer of funds from a bank savings account to a checking account…something that we do most of the time. It won’t be too elaborated like what’s currently being deployed in the banking system, but it is good enough to illustrate times when elaborated control of transaction is required. The below is the database diagram of tables, accompanied by the SQL scripts for MySQL DB that generates the tables which are involve in the transfer of funds from a savings account to a checking account. Both accounts can and will belong to a single customer. It is an inter-account transfer by the way.

Bank Account Fund Transfer Database Tables
Bean-Managed Transaction Bank Funds Transfer DB Diagram

Tables and Use Case Description

The tables diagram only consists of several tables that are related to perform the fund transfer. It is very watered-down and superficial; but for now, it is sufficient to capture the related entities.

Table NameEntity Class NameDescription
BANK_CUSTOMERSBankCustomerEntityThe customer record of the bank. For simplicity, we only capture both username and password field.

** In this demo, we’ll set the username and password are both “MadMax” and “passwd123″

BANK_ACCT_TYPEBankAcctTypeEntityThis is more like a statis data table which consists of various bank account types. For now, only “SAVINGS” and “CHECKING” account type is available. Please look at the data population script below.
BANK_ACCOUNTBankAccountEntityThe bank account of customers. It has fields like account no., the current balance, the foreign key will tie to the BANK_CUSTOMERS table to identify the ownership of the account. Both SAVINGS and CHECKING accounts are within this table. One customer could have multiple accounts.

** In this demo, “MadMax” has a savings account BK-001-09 with the balance of $9,000,000 and a checking account BK-012-10 with the balance of $1,000,000.

ACTIVITY_LOGActivityLogEntityThis is a table that records all of the activity of the customer with a timestamp when they perform a transaction and whether the transaction is a success or a failure.

** So, everytime when “MadMax” performs a transaction whether it goes through or not, the system will insert the record here as an activity log in this table.

BANK_GROUP_PROFITBankGroupProfitEntityThis table captures the profit (service charge/commission) made by the bank’s branch whenever a customer performs a transaction.

** For demo purposes, let’s have the bank’s initial balance of the profit to be $1bil and bank commission fixed at $2 for every fund transfer transaction performed. This $2 will be added upon the profit balance.

The SQL script for generating the tables as well as pre-populating the database with data is shown below. This script is for MySQL DB only. However if you are running on other database platform, you should be able to come out with your own script for experiment purposes by referring to the below:

MySQL Database Tables and Data Script

Stateless Session Bean Implementation

From here, let’s implement a stateless session bean BankTransactionSessionFacade with both the local and remote interfaces as IBankTransactionSessionFacadeLocal and IBankTransactionSessionFacadeRemote. The method to transfer the funds is transferFund() with the parameters such as bank customer entity object, the source account no., the target account no. and the amount to be transfered.

As for the logging process to the database, we’ll implement a stateless session bean call BankAuditTrailSessionFacade with both the local and remote interaces as IBankAuditTrailSessionFacadeLocal and IBankAuditTrailSessionFacadeRemote. This bean has a method call logActivity(), which records the activity done by the specific customer whether it is a success or failure back to the database. The method is annotated with @TransactionAttribute and should be defined as TransactionAttributeType.REQUIRES_NEW.

The process flow for the transferFund() method is as follow:

  1. First, the system will make a log record in the ACTIVITY_LOG table that shows the customer indeed had initiated a fund transfer instruction, call through IBankAuditTrailSessionFacadeLocal’s logActivity();
  2. The system will then check for the following:
    • The transfer amount must be greater than $0;
    • The source account no. provided must be a valid account;
    • The target account no. provided must be a valid account;
    • The source account must have the adequate funds to be transfered including the deduction of any bank charges (which is $2);
  3. If any of the criteria above is not met, FundTransferException will be thrown.
  4. The system will then proceed to deduct the transfer amount plus the bank commission charges in the source account.
  5. Next, the target account will be updated with the new balance amount minus the bank commission charges.
  6. The bank group profit will increase with commission charged for the transfer service.
  7. If any of the points failed in the above, the system will perform a rollback and all of the tables related except for ACTIVITY_LOG will be reversed to the previous state and no data changes will be committed. Failure will be recorded by calling the IBankAuditTrailSessionFacadeLocal’s logActivity() method.
  8. If it passed, the system will commit the changes to all the involved tables and the activity log will record a SUCCESS.

** If an error which is more severe comes up that has nothing to do with the business process, we just wrap it with a SystemProblemException.

The full Java codes for all the entity classes, the session bean and the interfaces are shown below:

Codes for BankCustomerEntity:

Codes for ActivityLogEntity:

Codes for BankAcctTypeEntity:

Codes for BankAccountEntity:

Codes for FundTransferExcepton:

Codes for SystemProblemException:

Codes for IBankAuditTrailSessionFacadeLocal

Codes for IBankAuditTrailSessionFacadeRemote:

Codes for BankAuditTrailSessionFacade:

Codes for IBankTransactionSessionFacadeLocal:

Codes for IBankTransactionSessionFacadeRemote

Codes for BankTransactionSessionFacade:
** Just take note that certain fraction of the codes could be refactored to another session bean and be reused. But I’m not going to do it this time, just to keep things simple for now.

Testing The Transaction Process

You may always write snippet of codes to test the transaction like the below:

Do play around with the value in transferAmount, the fromAccountNo and the toAccountNo and check for the validity of data in the database, you should be able to see the transaction in action.

Summary

So I hope that clears the fog of transaction demacrate REQUIRES_NEW. If your method in a session bean needs to be completed without being affected by either commit or rollback from the calling session bean, REQUIRES_NEW is the way to go.

I’ll continue to the discussion on TransactionAttributeType.MANDATORY in the second part of this series. So for now, do enjoy your brew of codes.

Related Articles

Born and currently resides in Malaysia, a seasoned Java developer whom had held positions as Senior Developer, Consultant and Technical Architect in various enterprise software development companies.