Search code examples
javajpaejbejb-3.0openjpa

Container Managed Transaction - Not marked for roll back


Bank.java

@Stateless
@Local
public class Bank implements IBank {

    @EJB
    IConfigBean iConfigBean;

    @EJB
    IDbs iDBS;

    @EJB
    IPosb iPosb;

    @Override
    public void doTransaction() {
        System.out.println("--Bank Transaction Started--");
        try {
            Config config1 = getConfig(1);
            iConfigBean.create(config1);

            iDBS.doDBSTransaction();

            Config config3 = getConfig(3);
            iConfigBean.create(config3);

            iPosb.doPOSBTransaction();

            Config config5 = getConfig(5);
            iConfigBean.create(config5);
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("---Bank Exception--");
        }
        System.out.println("--Bank Transaction End--");
    }

    @Override
    public Config getConfig(int inserttionOrderNo) {
        Config config = new Config();
        config.setType("EJBTransactionTESTING - " + inserttionOrderNo);
        return config;
    }
}

DBS.java

@Stateless
@Local
public class DBS implements IDbs {

    @EJB
    IConfigBean iConfigBean;

    @Override
    public void doDBSTransaction() {
        System.out.println("--DBS Transaction Started--");
        try {
            Config config2 = getConfig(2);
            iConfigBean.create(config2);
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("--DBS Exception--");
        }
        System.out.println("--DBS Transaction End--");
    }

    @Override
    public Config getConfig(int inserttionOrderNo) {
        Config config = new Config();
        config.setType("EJBTransactionTESTING - " + inserttionOrderNo);
        return config;
    }

}

POSB.java

@Stateless
@Local
public class POSB implements IPosb {

    @EJB
    IConfigBean iConfigBean;

    @Override
    public void doPOSBTransaction() {
        System.out.println("--POSB Transaction Started--");
        try {
            Config config4 = getConfig(4);
            iConfigBean.create(config4);
            if (true) {
                //For Test 1 
                //throw new NullPointerException(); 
            }
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("--POSB Exception--");
        }
        if (true) {
            //For Test 2 
            // throw new NullPointerException();
        }
        System.out.println("--POSB Transaction End--");
    }

    @Override
    public Config getConfig(int inserttionOrderNo) {
        Config config = new Config();
        config.setType("EJBTransactionTESTING - " + inserttionOrderNo);
        return config;
    }
}

I am new to Stack Overflow and Its my new question so correct me If I am wrong.

Environment is..

  • Windows 10
  • Java 1.8
  • Eclipse
  • Tomcat 8.5
  • EJB3

I have Three stateless bean, Please look at the Sequence Diagram of the Transaction flow.

I purposely making NullPointer Exception at two places during the transaction to know the difference and I have marked with Lightening Bold symbol in sequence diagram.

I am not using any @TransactionAttribute to any methods.

Test 1 - Null Pointer in Inside the try block (Lightening Bold symbol with Green) When I start the testing, Got Null pointer exception and all the transaction are not marked for roll back and data also got inserted in db.

I can only see Null pointer exception in the console log.

Test 2 - Null Pointer in Outside the try - catch method (Lightening Bold symbol with Red) When I start the testing, Got Null pointer exception plus EJBTransactionRolledbackException and all the transaction marked for roll back and no data inserted in db.

I can see NullPointer and EJBTransactionRolledback Exception in the console log.

Question here is,

  1. Why EJB transaction is not marked for roll back If I made Null pointer inside try block
  2. Why EJB transaction is roll back happens If I made null pointer outside try block

Thanks in advance.


Solution

  • Keep in mind EJB calls, all the "magic" made by container happens there, including transaction markup. It's possible due to the fact that EJB calls are not direct, but always go through proxy.

    You have such calls in your code:

    iPosb.doPOSBTransaction(); 
    

    So, if unchecked exception (NPE for example) is thrown in this method and not caught - it's ultimately caught by container due to EJB proxy wrapping the call above. In this case transaction only could be rolled back.

    Adding a call to a method of the same bean in your method (without using @EJB reference), does not change that:

    @Override
    public void doPOSBTransaction() {
        try {
            Config config4 = getConfig(4);
            iConfigBean.create(config4);
            if (true) {
                newMethod(); 
            }
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("--POSB Exception--");
        }
        if (true) {
            newMethod(); 
        }        
    }
    
    private void newMethod(){
        throw new RuntimeException();
    }
    

    You can easily check that commit/rollback behaviour is just the same in this case, no matter that a method is added to call stack.

    So the important thing you have to remember is that all container tricks work only on @EJB calls. So, for example, it's pointless to place transactional annotation on a private method - it won't be used ever.

    Another important point is about checked exceptions. By default these do not cause transaction rollback indeed. But it's still possible to annotate your checked exception like below to make it rollback ongoing transaction:

    @ApplicationException(rollback = true)