A deeper dive into JPA, 2-Phase-Commit [ 2PC ] and RAC

Overview JPA and 2-Phase-Commit

Mike Keith Architect at Oracle and Author

Pro JPA 2: Mastering the Java Persistence API (Second edition)

summarizes the usage of JPA in a distributed evironment the following :

  • A JPA application will get the 2PC benefits the same as any other application
  • The peristence unit data source is using JTA and  is configured to use an XA data source
  • The XA resources and transaction manager 2PC interactions happen on their own without the JPA EMF knowing or having to be involved.
  • If a 2PC XA tx fails then an exception will be thrown just the same as if the tx was optimized to not have 2PC.

This was enough motivation for me working on Oracle RAC and JDBC projects to have a closer look on JPA and 2PC.

Versions used  / Configuration File persistence.xml

Wildfly:  8.2
Hibernate Version: 4.3.7.Final
--> Collecting Data for RAC database1
    Driver Name             : Oracle JDBC driver
    Driver Version          : 12.1.0.2.0
    Database Product Version: Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production
    DB Name:  BANKA
    1. Instance Name: bankA_2 - Host: hract21.example.com - Pooled XA Connections: 61

--> Collecting Data for RAC database2
    Driver Name             : Oracle JDBC driver
    Driver Version          : 12.1.0.2.0
    Database Product Version: Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production
    DB Name:  BANKB
    1. Instance Name: bankb_3 - Host: hract21.example.com - Pooled XA Connections: 62

persistence.xml

<?xml version="1.0"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
             version="2.0">

    <persistence-unit name="RacBankAHibPU" transaction-type="JTA">
        <provider>org.hibernate.ejb.HibernatePersistence</provider>
        <jta-data-source>java:/jboss/datasources/xa_rac12g_banka</jta-data-source>
        <class>com.hhu.wfjpa2pc.Accounts</class>
        <properties>
            <property name="hibernate.transaction.jta.platform"
                 value="org.hibernate.service.jta.platform.internal.JBossAppServerJtaPlatform" />
            <property name="hibernate.show_sql" value="true" />
            <property name="hibernate.dialect" value="org.hibernate.dialect.Oracle10gDialect"/>
        </properties>
    </persistence-unit>
    <persistence-unit name="RacBankBHibPU" transaction-type="JTA">
        <provider>org.hibernate.ejb.HibernatePersistence</provider>
        <jta-data-source>java:/jboss/datasources/xa_rac12g_bankb</jta-data-source>
        <class>com.hhu.wfjpa2pc.Accounts</class>
        <properties>
            <property name="hibernate.transaction.jta.platform"
                 value="org.hibernate.service.jta.platform.internal.JBossAppServerJtaPlatform" />
            <property name="hibernate.show_sql" value="true" />
            <property name="hibernate.dialect" value="org.hibernate.dialect.Oracle10gDialect"/>
        </properties>
    </persistence-unit>
</persistence>

Running a successful 2PC operation with JPA

Call Flow 

- Get EntityManager for RAC Database1 [ em1=getEntityManager1(); ]
- Get EntityManager for RAC Database2 [ em2=getEntityManager2(); ]
- Start as Usertransacation             [ ut.begin(); ]
- Join transaction from EntityManager 1  [ em1.joinTransaction(); ]
- Join transaction from EntityManager 2  [ em2.joinTransaction(); ]
- Chance Balance on both databases
bankA_acct.setBalance( bankA_acct.getBalance().add(b) );
em1.merge(bankA_acct);
if (isEnableFlush() )
em1.flush();

bankB_acct.setBalance( bankB_acct.getBalance().subtract(b) );
em2.merge(bankB_acct);
if (isEnableFlush() )
em2.flush();
- Finally commit the Transaction [ ut.commit(); ]

Application log :
14:51:58.071 transferMoneyImpl():: Found both Entity Managers for PUs : RacBankAHibPU and RacBankBHibPU
14:51:58.074 transferMoneyImpl():: Account at bank A: User99_at_BANKA - Balance: 10000
14:51:58.075 transferMoneyImpl():: Account at bank B: User98_at_BANKB - Balance: 10000
14:51:58.076 transferMoneyImpl():: Both EMs joined our XA Transaction...
14:51:58.092 transferMoneyImpl():: Before Commit ...
14:51:58.160 transferMoneyImpl():: Tx Commit worked !
14:51:58.165 Database Name:BANKA -- Account: User99_at_BANKA -- Balance: 11000.0
14:51:58.168 Database Name:BANKB -- Account: User98_at_BANKB -- Balance: 9000.0
14:51:58.169 transferMoneyImpl():: Leaving with TX Status:: [UT status:  6 - STATUS_NO_TRANSACTION]

-> We successfully managed to transfer some money from bankA to bankB !

Testing Rollback operation with EM flush enabled [ transaction status : STATUS_MARKED_ROLLBACK ]

Account Balance
 transferMoneyImpl():: Account at bank A: User99_at_BANKA - Balance: 20000
 transferMoneyImpl():: Account at bank B: User98_at_BANKB - Balance: 0
Note the next money transfer/transaction should trigger a constraint violation ! 

Call Flow
- Get EntityManager for RAC Database1 [ em1=getEntityManager1(); ]
- Get EntityManager for RAC Database2 [ em2=getEntityManager2(); ]
- Start a User transaction             [ ut.begin(); ] 
- Join transaction from EntityManager 1  [ em1.joinTransaction(); ]
- Join transaction from EntityManager 2  [ em2.joinTransaction(); ]
- Chance Balance on both databases
     bankA_acct.setBalance( bankA_acct.getBalance().add(b) );
        em1.merge(bankA_acct);
        if (isEnableFlush() )
          em1.flush();
                
        bankB_acct.setBalance( bankB_acct.getBalance().subtract(b) );
        em2.merge(bankB_acct);           
        if (isEnableFlush() )
          em2.flush();              
- em2.flush is failing due to a constraint violation and set the TX status to  : STATUS_MARKED_ROLLBACK 
   Error : org.hibernate.exception.ConstraintViolationException: could not execute statement
- Exception handler checks transaction status : STATUS_MARKED_ROLLBACK and is rolling back the TX
       if ( status != javax.transaction.Status.STATUS_NO_TRANSACTION   ) 
         {
         ut.rollback();
         ...
- After rollback() transaction status changed to   STATUS_NO_TRANSACTION                      
  
Application log :
15:11:03.920 transferMoneyImpl():: Found both Entity Managers for PUs : RacBankAHibPU and RacBankBHibPU
15:11:03.929 transferMoneyImpl():: Account at bank A: User99_at_BANKA - Balance: 20000
15:11:03.931 transferMoneyImpl():: Account at bank B: User98_at_BANKB - Balance: 0
15:11:03.931 transferMoneyImpl():: Both EMs joined our XA Transaction... 
15:11:03.960 transferMoneyImpl():: FATAL ERROR - Tx Status : [UT status:  1 - STATUS_MARKED_ROLLBACK]
15:11:03.962 transferMoneyImpl():: Before TX rollback ... 
15:11:03.974 transferMoneyImpl():: TX rollback worked !
15:11:03.974 transferMoneyImpl():: Leaving with TX Status:: [UT status:  6 - STATUS_NO_TRANSACTION]


Exception stack :
15:11:03.960 Error in top level function: transferMoneyImpl():: 
15:11:03.960 org.hibernate.exception.ConstraintViolationException: could not execute statement
15:11:03.961 javax.persistence.PersistenceException: org.hibernate.exception.ConstraintViolationException: could not execute statement
    at org.hibernate.jpa.spi.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1763)
    at org.hibernate.jpa.spi.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1677)
    at org.hibernate.jpa.spi.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1683)
    at org.hibernate.jpa.spi.AbstractEntityManagerImpl.flush(AbstractEntityManagerImpl.java:1338)
    at com.hhu.wfjpa2pc.Jpa2pcTest.transferMoneyImpl(Jpa2pcTest.java:235)
    at com.hhu.wfjpa2pc.Jpa2pcTest.transferMoney(Jpa2pcTest.java:166)
        ..
Caused by: org.hibernate.exception.ConstraintViolationException: could not execute statement
    at org.hibernate.exception.internal.SQLExceptionTypeDelegate.convert(SQLExceptionTypeDelegate.java:72)
    at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java
    ... 
Caused by: java.sql.SQLIntegrityConstraintViolationException: ORA-02290: check constraint (SCOTT.S_LOWER_CHK) violated

Testing Rollback operation without EM flush enabled [ transaction status : STATUS_NO_TRANSACTION  ]

Account Balance
 transferMoneyImpl():: Account at bank A: User99_at_BANKA - Balance: 20000
 transferMoneyImpl():: Account at bank B: User98_at_BANKB - Balance: 0
Note the next money transfer/transaction should trigger a constraint violation ! 

Call Flow
- Get EntityManager for RAC Database1 [ em1=getEntityManager1(); ]
- Get EntityManager for RAC Database2 [ em2=getEntityManager2(); ]
- Start a User transaction            [ ut.begin(); ] 
- Join transaction from EntityManager 1  [ em1.joinTransaction(); ]
- Join transaction from EntityManager 2  [ em2.joinTransaction(); ]
- Chance Balance on both databases
     bankA_acct.setBalance( bankA_acct.getBalance().add(b) );
        em1.merge(bankA_acct);
        if (isEnableFlush() )
          em1.flush();
                
        bankB_acct.setBalance( bankB_acct.getBalance().subtract(b) );
        em2.merge(bankB_acct);           
        if (isEnableFlush() )
          em2.flush();        
- Commit the Transaction [ ut.commit(); ] fails with :  ARJUNA016053: Could not commit transaction.
- As the Commit itself fails Wildfly rollback the transaction 
- Tx Status after COMMIT error :  STATUS_NO_TRANSACTION 
- Exception handler checks transaction status : STATUS_MARKED_ROLLBACK and is not rolling back the TX
       if ( status != javax.transaction.Status.STATUS_NO_TRANSACTION   ) 
         {
         ut.rollback();
         ...
- Here we don't run any rollback operation -> the TX status remains at   STATUS_NO_TRANSACTION                      
  
Application log :
  15:27:53.818 transferMoneyImpl():: Found both Entity Managers for PUs : RacBankAHibPU and RacBankBHibPU
  15:27:53.827 transferMoneyImpl():: Account at bank A: User99_at_BANKA - Balance: 20000
  15:27:53.829 transferMoneyImpl():: Account at bank B: User98_at_BANKB - Balance: 0
  15:27:53.829 transferMoneyImpl():: Both EMs joined our XA Transaction... 
  15:27:53.829 transferMoneyImpl():: Before Commit ... 
  15:27:53.857 transferMoneyImpl():: FATAL ERROR - Tx Status : [UT status:  6 - STATUS_NO_TRANSACTION]
  15:27:53.859 transferMoneyImpl():: TX not active / TX already rolled back
  15:27:53.859 transferMoneyImpl():: Leaving with TX Status:: [UT status:  6 - STATUS_NO_TRANSACTION]

Testing transaction Recovery with JPA

What we are expecting  and what we are testing
  - Transaction Timeout is set to 600 seconds
  - We set a breakpoint at   OracleXAResource.commit
    ==> This means Wildfly has written a COMMIT record to  the  Wildlfly LOG-STORE
  - After stop at the first OracleXAResource.commit breakpoint  we kill the Wildfly server 
  - Both RMs [ Oracle RAC databases ] are now counting down the Transaction Timeout 
  - If Timeout is reached the failed transaction becomes visible in dba_2pc_pending table
  - Trying to get a lock on these records should lead to a ORA-1591 error 
  - After Wildfly restart the Periodic Recovery should run OracleXAResource.commit and release all locks

Preparing and running the test scenario

Start Wildfly in Debug Mode :
Set breakpoint on OracleXAResource.commit and run the application

Stack Trace 
"default task-3"
oracle.jdbc.xa.client.OracleXAResource.commit(OracleXAResource.java:553)
org.jboss.jca.adapters.jdbc.xa.XAManagedConnection.commit(XAManagedConnection.java:338)
org.jboss.jca.core.tx.jbossts.XAResourceWrapperImpl.commit(XAResourceWrapperImpl.java:107)
com.arjuna.ats.internal.jta.resources.arjunacore.XAResourceRecord.topLevelCommit(XAResourceRecord.java:461)
com.arjuna.ats.arjuna.coordinator.BasicAction.doCommit(BasicAction.java:2810)
com.arjuna.ats.arjuna.coordinator.BasicAction.doCommit(BasicAction.java:2726)
com.arjuna.ats.arjuna.coordinator.BasicAction.phase2Commit(BasicAction.java:1820)
com.arjuna.ats.arjuna.coordinator.BasicAction.End(BasicAction.java:1504)
com.arjuna.ats.arjuna.coordinator.TwoPhaseCoordinator.end(TwoPhaseCoordinator.java:96)
com.arjuna.ats.arjuna.AtomicAction.commit(AtomicAction.java:162)
com.arjuna.ats.internal.jta.transaction.arjunacore.TransactionImple.commitAndDisassociate(TransactionImple.java:1166)
com.arjuna.ats.internal.jta.transaction.arjunacore.BaseTransaction.commit(BaseTransaction.java:126)
com.arjuna.ats.jbossatx.BaseTransactionManagerDelegate.commit(BaseTransactionManagerDelegate.java:75)
org.jboss.tm.usertx.client.ServerVMClientUserTransaction.commit(ServerVMClientUserTransaction.java:173)
com.hhu.wfjpa2pc.Jpa2pcTest.transferMoneyImpl(Jpa2pcTest.java:242)
com.hhu.wfjpa2pc.Jpa2pcTest.transferMoney(Jpa2pcTest.java:166)

Wildfly Check for prepared transaction 
$ $WILDFLY_HOME/bin/jboss-cli.sh --connect --file=list_prepared_xa_tx.cli
{"outcome" => "success"}
0:ffffc0a805c9:f5a10ef:56039e68:d

Locate and kill JBOSS server process 
0 S oracle    5875  5821  7  80   0 - 413473 futex_ 08:55 ?       00:00:30 
     /usr/java/latest/bin/java .... -Djboss.server.base.dir=/usr/local/wildfly-8.2.0.Final/standalone -c standalone.xml
0 S oracle    6174  5680  0  80   0 - 25827 pipe_w 09:02 pts/1    00:00:00 grep java
[oracle@wls1 WILDFLY]$ kill -9 5875

Now wait [ at lest 600 seconds ] until the Transaction becomes visible in  dba_2pc_pending

SQL> SELECT * FROM GLOBAL_NAME;
GLOBAL_NAME
----------------
BANKA

SQL> select * from dba_2pc_pending;
LOCAL_TRAN_ID           GLOBAL_TRAN_ID                            STATE         MIX A TRAN_COMMENT
---------------------- ---------------------------------------------------------------- ---------------- --- - ----------------
FAIL_TIM FORCE_TI RETRY_TI OS_USER    OS_TERMINAL  HOST          DB_USER       COMMIT#
-------- -------- -------- ------------ ------------ ---------------- ------------ ----------------
9.21.7139           131077.00000000000000000000FFFFC0A805C90F5A10EF56039E680000000D3 prepared     no
               1
09:07:22      09:15:34 oracle    unknown      wls1.example.com           43619336



SQL> SELECT * FROM GLOBAL_NAME;
GLOBAL_NAME
----------------
BANKB

SQL> select * from dba_2pc_pending;
LOCAL_TRAN_ID           GLOBAL_TRAN_ID                            STATE         MIX A TRAN_COMMENT
---------------------- ---------------------------------------------------------------- ---------------- --- - ----------------
FAIL_TIM FORCE_TI RETRY_TI OS_USER    OS_TERMINAL  HOST          DB_USER       COMMIT#
-------- -------- -------- ------------ ------------ ---------------- ------------ ----------------
4.15.3293           131077.00000000000000000000FFFFC0A805C90F5A10EF56039E680000000D3 prepared     no
               1
09:07:22      09:15:34 oracle    unknown      wls1.example.com           20931538

Check for locks 
-> Connected to  scott/tiger@ract2-scan.grid12c.example.com:1521/banka
select * from accounts for update
*
ERROR at line 1:
ORA-01591: lock held by in-doubt distributed transaction 9.21.7139


-> Connected to  scott/tiger@ract2-scan.grid12c.example.com:1521/bankb
select * from accounts for update
*
ERROR at line 1:
ORA-01591: lock held by in-doubt distributed transaction 4.15.3293


Restart Wildfly in Debug Mode and let the Periodic Recovery Thread commit the transaction 

"Periodic Recovery"
oracle.jdbc.xa.client.OracleXAResource.commit(OracleXAResource.java:553)
org.jboss.jca.adapters.jdbc.xa.XAManagedConnection.commit(XAManagedConnection.java:338)
org.jboss.jca.core.tx.jbossts.XAResourceWrapperImpl.commit(XAResourceWrapperImpl.java:107)
com.arjuna.ats.internal.jta.resources.arjunacore.XAResourceRecord.topLevelCommit(XAResourceRecord.java:461)
com.arjuna.ats.arjuna.coordinator.BasicAction.doCommit(BasicAction.java:2810)
com.arjuna.ats.arjuna.coordinator.BasicAction.doCommit(BasicAction.java:2726)
com.arjuna.ats.arjuna.coordinator.BasicAction.phase2Commit(BasicAction.java:1820)
com.arjuna.ats.arjuna.recovery.RecoverAtomicAction.replayPhase2(RecoverAtomicAction.java:71)
com.arjuna.ats.internal.arjuna.recovery.AtomicActionRecoveryModule.doRecoverTransaction(AtomicActionRecoveryModule.java:152)
com.arjuna.ats.internal.arjuna.recovery.AtomicActionRecoveryModule.processTransactionsStatus(AtomicActionRecoveryModule.java:253)
com.arjuna.ats.internal.arjuna.recovery.AtomicActionRecoveryModule.periodicWorkSecondPass(AtomicActionRecoveryModule.java:109)
com.arjuna.ats.internal.arjuna.recovery.PeriodicRecovery.doWorkInternal(PeriodicRecovery.java:789)
com.arjuna.ats.internal.arjuna.recovery.PeriodicRecovery.run(PeriodicRecovery.java:371)

-> WildFly Thread Periodic Recovery stops at OracleXAResource.commit
-> Press Debugger Command : Continue 
-> WildFly Thread Periodic Recovery has committed Transaction Branch 1
-> WildFly Thread Periodic Recovery stops again  at .OracleXAResource.commit
-> Press Debugger Command : Continue 
-> WildFly Thread Periodic Recovery has committed Transaction Branch 2
-> Complete Transaction is now committed 

Verify Access to the Database records and Wildfly Prepared Transaction Cleanup 
-> Connected to  scott/tiger@ract2-scan.grid12c.example.com:1521/banka
ACCOUNT                 BALANCE
-------------------------------- ----------
User99_at_BANKA               14000


-> Connected to  scott/tiger@ract2-scan.grid12c.example.com:1521/bankb
ACCOUNT                 BALANCE
-------------------------------- ----------
User98_at_BANKB                6000


List prepared Transaction
$  $WILDFLY_HOME/bin/jboss-cli.sh --connect --file=list_prepared_xa_tx.cli
{"outcome" => "success"}

-> After a successful transaction recovery the locks are gone 

 

Java Code

public void transferMoneyImpl()
      {
        String methodName = "transferMoneyImpl():: ";
        EntityManager em1;
        EntityManager em2;
        UserTransaction ut =null;
      try
        {
        setRunTimeInfo(methodName  + "Entering ... ");
            
        HttpSession session = (HttpSession) FacesContext.getCurrentInstance().getExternalContext().getSession(true);
        if ( session == null)
              {
                throw new IllegalArgumentException(methodName+ ": Could not get HTTP session : ");    
              }                        
        final Object lock = session.getId().intern();       
        synchronized(lock) 
              {
                em1=getEntityManager1();
                em2=getEntityManager2();
                    //
                    // Note even we get an EntityManager Object we still not sure that the 
                    // EntityManager Could open connection the underlying JDBC connection !
                    //
                if ( em1 == null )
                    setRunTimeInfo(methodName  + "Faild to get EM for PU: " + EMF.getPU1() );
                else if ( em2 == null )
                    setRunTimeInfo(methodName  + "Faild to get EM for PU: " + EMF.getPU2() );
                else
                    setRunTimeInfo(methodName  + "Found both Entity Managers for PUs : " + 
                       EMF.getPU1()  + " and " +  EMF.getPU2()  ); 
                   
                 
                String bankA_acct_name = "User99_at_BANKA";
                Accounts bankA_acct = em1.find(Accounts.class, bankA_acct_name);
                if ( bankA_acct == null)
                    { 
                    setRunTimeInfo(methodName + "Could not locate Account at bankA : " + bankA_acct_name );
                    return;
                    }
                setRunTimeInfo(methodName  +"Account at bank A: " + bankA_acct.getAccount()  + " - Balance: " +  bankA_acct.getBalance() );
                
                String bankB_acct_name = "User98_at_BANKB";
                Accounts bankB_acct = em2.find(Accounts.class, bankB_acct_name);
                if ( bankB_acct == null)
                    { 
                    setRunTimeInfo(methodName + "Could not locate Account at bankB : " + bankB_acct_name );
                    return;
                    }
                setRunTimeInfo(methodName  +"Account at bank B: " + bankB_acct.getAccount()  + " - Balance: " +  bankB_acct.getBalance() );
              
                ut  = (javax.transaction.UserTransaction)new InitialContext().lookup("java:comp/UserTransaction"); 
                    // Set tranaction time to 120 seconds to avoid any timeouts during testing -
                    // especially when testing transaction recovery by restarting Wildfly server 
                    // Note as we kill the JAVA process both RMs will wait 120 s before Tx becomes visible in dba_2pc_pending 
                int tx_timeout = 120;
                ut.setTransactionTimeout(tx_timeout);
                ut.begin();
                em1.joinTransaction();
                em2.joinTransaction();
                setRunTimeInfo(methodName  + "Both EMs joined our XA Transaction... - TX Timeout: " + tx_timeout );
                BigDecimal b = new BigDecimal(1000);
                bankA_acct.setBalance( bankA_acct.getBalance().add(b) );
                em1.merge(bankA_acct);
                if (isEnableFlush() )
                    em1.flush();
                
                bankB_acct.setBalance( bankB_acct.getBalance().subtract(b) );
                em2.merge(bankB_acct);           
                if (isEnableFlush() )
                    em2.flush();
                
                setRunTimeInfo(methodName  + "Before Commit ... ");                
                ut.commit();
                setRunTimeInfo(methodName  + "Tx Commit worked !");
                checkBalanceImpl();
              }
        } catch ( Throwable t1)
          { 
            try
              {    
              String tx_status = returnTXStatus(ut);
              setRunTimeInfo( methodName  + "FATAL ERROR - Tx Status : " + tx_status  );
                // Use Throwable as we don't want to loose any important imformation
                // Note: Throwable is super class of Exception class          
               genericException("Error in top level function: " + methodName , (Exception)t1);                          
               if ( ut != null )
                  {
                    int status = ut.getStatus();    
                        // rollback transaction if still active - if not do nothing 
                    if ( status != javax.transaction.Status.STATUS_NO_TRANSACTION   ) {
                        setRunTimeInfo(methodName  + "Before TX rollback ... ");
                        ut.rollback();
                        setRunTimeInfo(methodName  + "TX rollback worked !");
                    } else
                        setRunTimeInfo(methodName  + "TX not active / TX already rolled back");
                  }
              }  catch ( Throwable t2)
                 { 
                   genericException(methodName + "FATAL ERROR during ut.rollback() ", (Exception)t2); 
                 } 
          }
        closeEntityManagers();       
        String tx_status_exit = "";
        try
          {    
            tx_status_exit = returnTXStatus(ut);
          }   catch ( Throwable t3)
            { 
              genericException(methodName + " Error during returning TX status ", (Exception)t3); 
            }    
        setRunTimeInfo(methodName  + "Leaving with TX Status:: " + tx_status_exit );
      }

Reference

Hibernate / JPA : Access to DialectResolutionInfo cannot be null

Pom.xml 
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-core</artifactId>
            <version>4.3.6.Final</version>
        </dependency>

        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-entitymanager</artifactId>
            <version>4.3.6.Final</version>
        </dependency>


Error and Cause Determination 

Deploying a new WEBApplication using Hibernate fails with : 
Deploying /usr/local/wildfly-8.2.0.Final/standalone/deployments/WFJPA2PC-1.0.war
"{\"JBAS014671: Failed services\" => {\"jboss.persistenceunit.\\\"WFJPA2PC-1.0.war#RacBankBHibPU\\\"\" 
  => \"org.jboss.msc.service.StartException in service jboss.persistenceunit.\\\"WFJPA2PC-1.0.war#RacBankBHibPU\\\": 
     org.hibernate.HibernateException: Access to DialectResolutionInfo cannot be null when 'hibernate.dialect' not set
     Caused by: org.hibernate.HibernateException: Access to DialectResolutionInfo cannot be null when 'hibernate.dialect' not set\"}}"

Cause : Oracle RAC database was down ! 

Fix : Add hibernate.dialect to your persistence.xml 
   <persistence-unit name="RacBankBHibPU" transaction-type="JTA">
        <provider>org.hibernate.ejb.HibernatePersistence</provider>
        <jta-data-source>java:/jboss/datasources/xa_rac12g_bankb</jta-data-source>
        <class>com.hhu.wfjpa2pc.Accounts</class>
        <properties>
            <property name="hibernate.transaction.jta.platform"
                 value="org.hibernate.service.jta.platform.internal.JBossAppServerJtaPlatform" />
            <property name="hibernate.show_sql" value="true" />
            <property name="hibernate.dialect" value="org.hibernate.dialect.Oracle10gDialect"/>
        </properties>
    </persistence-unit>

-> Now application can be redeployed 
-> Application now starts but throws the following stack 
08:55:04.761 javax.resource.ResourceException: IJ000453: Unable to get managed connection for java:jboss/datasources/xa_rac12g_bankb
08:55:04.762 java.sql.SQLException: javax.resource.ResourceException: IJ000453: Unable to get managed connection for java:jboss/datasources/xa_rac12g_bankb
    at org.jboss.jca.adapters.jdbc.WrapperDataSource.getConnection(WrapperDataSource.java:154)
    at com.hhu.wfjpa2pc.Jpa2pcTest.getRacInfoDS(Jpa2pcTest.java:398)
    at com.hhu.wfjpa2pc.Jpa2pcTest.getRacInfo(Jpa2pcTest.java:376)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.

-> With above Stack we easily can figure out that we need to start our RAC DB

A deeper dive into Transaction Timeouts with JEE7,Wildfly and Oracle RAC

Potential Transaction Timeouts in a JEE7 / RAC 12.1.0.2 env

  1.  JTA Transaction Timeout triggering an OracleXAResource.rollback operation on our TM side [ Wildfly 8.2 ]
  2. JTA Transaction Timeout occuring on the RM side [ ORA-24756 ]
  3. DISTRIBUTED LOCK TIMEOUT occuring on the RM side [ ORA-2049 ]
  4. HTTPSession Object Timeout

Rule of Thumb

  •  JTA transaction timeout  <  DISTRIBUTED_LOCK_TIMEOUT  <  HTTP Session Timeout
  • In a working JTA env we only want to see JTA timeouts triggered by our TM  [ see 1. ]

Overview Bean Managed Transaction with JTA

  • Each JTA transaction is associated with an Execution Thread
  • Only a single Transaction can be active within an  Execution Thread
  • If one Transaction is active the user can’t start an new one until the TX was committed suspended or rolled back
  • The setTransactionTimeout() method causing  the transaction to roll back after reaching the timeout limit
  • Do not invoke the getRollbackOnly and setRollbackOnly methods of the EJBContext interface in bean-managed transactions [ BMT ]
  • For bean-managed transactions, invoke the getStatus and rollback methods of the UserTransaction interface
  • If a session times out within Oracle, then the Oracle database Process Monitor (PMON) will delete the transaction branch if not prepared or cause the transaction to become in-doubt if prepared.
  • We can track the Transaction Timeout at RM side using Event 10246
         alter system set event=”10246 trace name context forever, level 1″ scope=spfile sid=’*’;
  • If the PMON terminates the transaction branch you should get error: ORA-24756: transaction does not exist
  • If the Transaction can’t get a database lock you should get error: ORA-02049: timeout: distributed transaction waiting for lock
  • During this JPA test we use OnePhaseCommit [ means no 2PC / no prepare call ]
  • We always want that our TM is controlling the Transaction
  • If runnig with correct timeout settings we should not see any ORA-2049 or ORA-24756 errors

How to track the XA transaction at database level

  • FORMATID, GLOBALID, BRANCHID build our  XA Xid
  • TIGHTLY COUPLED means we serialize using a DX lock [ can lead to a performance problem ]  and can use RAC ClusterWide Transaction Feature
  • PREPARECOUNT=0 this Tx is not yet prepared
  • STATE ACTIVE means this transaction is active
SQL> select FORMATID, GLOBALID, BRANCHID, BRANCHES, REFCOUNT, PREPARECOUNT, STATE, 
     FLAGS, COUPLING  from v$global_transaction;

  FORMATID GLOBALID
---------- ----------------------------------------------------------------
BRANCHID
--------------------------------------------------------------------------------------------------------------------------------
  BRANCHES   REFCOUNT PREPARECOUNT STATE                       FLAGS COUPLING
---------- ---------- ------------ -------------------------------------- ---------- ---------------
    131077 00000000000000000000FFFFC0A805C935ABDAA855FBBB600000046931
00000000000000000000FFFFC0A805C935ABDAA855FBBB600000046C0000000200000000
     1        1         0 ACTIVE                       0 TIGHTLY COUPLED

TEST 1:  Testing global JTA Timeout

  • The service routine will run 60 seconds before committing the data
  • The JPA timeout will rollback the transaction after the JTA timeout [ 30 seconds ]
  • The Transaction Reaper Wildfly Thread will rollback the transaction after 30 second
  • Later on the commit fails as we don’t run inside a transaction anymore
Setup
HTTP Session Timeout       : 1800 seconds
JTA Timeout                :   30 seconds
ut.setTransactionTimeout() :  not active
Service routine sleep      :   60 seconds 

Setup and verify  JTA Transaction Timeout : 30 seconds:

$ $WILDFLY_HOME/bin/jboss-cli.sh  --connect --controller=localhost:9990 '/subsystem=transactions/:write-attribute(name=default-timeout,value=30)';
{
    "outcome" => "success",
    "result" => 30,
    "response-headers" => {"process-state" => "reload-required"}
}

$  $WILDFLY_HOME/bin/jboss-cli.sh  --connect --controller=localhost:9990 '/subsystem=transactions/:read-attribute(name=default-timeout)'
{
    "outcome" => "success",
    "result" => 30
}

-> Restart Wildfly
-> Run test test case
 
Wildfly Logs:
10:18:52,211 INFO  [stdout] (default task-15) +++ HttpSessionListenerImpl:: sessionCreated()....
10:18:55,489 INFO  [org.eclipse.persistence.connection] (default task-36) 
        Connected: jdbc:oracle:thin:@ract2-scan.grid12c.example.com:1521/banka
    User: SCOTT
    Database: Oracle  Version: Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production
    Driver: Oracle JDBC driver  Version: 12.1.0.2.0
...
2015-09-17 10:18:55,500 INFO  [org.eclipse.persistence.connection] (default task-17) vfs:/usr/local/wildfly-8.2.0.Final/standalone/deployments/WFJPA2EL-1.0.war/WEB-INF/classes/_jpaELPU login successful
10:19:27,365 WARN  [com.arjuna.ats.arjuna] (Transaction Reaper) ARJUNA012117: TransactionReaper::check timeout for TX 0:ffffc0a805c9:6a216e79:55fa71c5:72 in state  RUN
10:19:27,367 WARN  [com.arjuna.ats.arjuna] (Transaction Reaper Worker 0) ARJUNA012095: Abort of action id 0:ffffc0a805c9:6a216e79:55fa71c5:72 invoked while multiple threads active within it.
10:19:27,368 WARN  [com.arjuna.ats.arjuna] (Transaction Reaper Worker 0) ARJUNA012108: CheckedAction::check - atomic action 0:ffffc0a805c9:6a216e79:55fa71c5:72 aborting with 1 threads active!
10:19:27,383 WARN  [com.arjuna.ats.arjuna] (Transaction Reaper Worker 0) ARJUNA012121: TransactionReaper::doCancellations worker Thread[Transaction Reaper Worker 0,5,main] successfully canceled TX 0:ffffc0a805c9:6a216e79:55fa71c5:72
10:19:57,381 WARN  [com.arjuna.ats.arjuna] (default task-17) ARJUNA012077: Abort called on already aborted atomic action 0:ffffc0a805c9:6a216e79:55fa71c5:72

-> JPA connection returned at 10:18:55
   About 32 seconds later the global JTA timeout kicks in and terminates the transaction a

Stack :
10:19:57.382 Error in top level function: timeoutTest()
10:19:57.384 ARJUNA016102: The transaction is not active! Uid is 0:ffffc0a805c9:6a216e79:55fa71c5:72
10:19:57.385 javax.transaction.RollbackException: ARJUNA016102: The transaction is not active! Uid is 0:ffffc0a805c9:6a216e79:55fa71c5:72
    at com.arjuna.ats.internal.jta.transaction.arjunacore.TransactionImple.commitAndDisassociate(TransactionImple.java:1156)
    at com.arjuna.ats.internal.jta.transaction.arjunacore.BaseTransaction.commit(BaseTransaction.java:126)
    at com.arjuna.ats.jbossatx.BaseTransactionManagerDelegate.commit(BaseTransactionManagerDelegate.java:75)
    at org.jboss.tm.usertx.client.ServerVMClientUserTransaction.commit(ServerVMClientUserTransaction.java:173)
    at com.hhu.wfjpa2el.JPATestBean.timeoutTest(JPATestBean.java:456)

TEST 2: Overriding global JTA Timeout with  ut.setTransactionTimeout()

  • This test is similar to TEST 1
  • it only overrides the JTA transaction timeout with setTransactionTimeout()
Setup 
HTTP Session Timeout       : 1800 seconds
JTA Timeout                :   30 seconds
ut.setTransactionTimeout() :   15 seconds
Service routine sleep      :   60 seconds 

Java Code 
     ...
     ut  = (UserTransaction)new InitialContext().lookup("java:comp/UserTransaction");          
     ut.setTransactionTimeout(getTxTimeout());                     
     ut.begin();
     ...

Wildfly Log :
2015-09-17 10:58:58,426 INFO  [org.eclipse.persistence.connection] (default task-36) 
        Connected: jdbc:oracle:thin:@ract2-scan.grid12c.example.com:1521/banka
    User: SCOTT
    Database: Oracle  Version: Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production
    Driver: Oracle JDBC driver  Version: 12.1.0.2.0
2015-09-17 10:58:58,431 INFO  [org.eclipse.persistence.connection] (default task-36) vfs:/usr/local/wildfly-8.2.0.Final/standalone/deployments/WFJPA2EL-1.0.war/WEB-INF/classes/_jpaELPU login successful
2015-09-17 10:59:15,094 WARN  [com.arjuna.ats.arjuna] (Transaction Reaper) ARJUNA012117: TransactionReaper::check timeout for TX 0:ffffc0a805c9:6a216e79:55fa71c5:11a in state  RUN
2015-09-17 10:59:15,096 WARN  [com.arjuna.ats.arjuna] (Transaction Reaper Worker 0) ARJUNA012095: Abort of action id 0:ffffc0a805c9:6a216e79:55fa71c5:11a invoked while multiple threads active within it.
2015-09-17 10:59:15,097 WARN  [com.arjuna.ats.arjuna] (Transaction Reaper Worker 0) ARJUNA012108: CheckedAction::check - atomic action 0:ffffc0a805c9:6a216e79:55fa71c5:11a aborting with 1 threads active!
2015-09-17 10:59:15,115 WARN  [com.arjuna.ats.arjuna] (Transaction Reaper Worker 0) ARJUNA012121: TransactionReaper::doCancellations worker Thread[Transaction Reaper Worker 0,5,main] successfully canceled TX 0:ffffc0a805c9:6a216e79:55fa71c5:11a

-> JPA connection returned at  10:58:58,
-> About 17 seconds later the Timeout value set by ut.setTransactionTimeout()  kicks in and terminates the transaction a
-> As in TEST 1 the commit fail with a similar stack  

TEST 3 : Stop all TM Threads and  let the RM [ Resource Manager ] terminate the transaction

  • In this test set a breakpoint at OracleXAResource.commit to stop all WildFly Threads
  • This will avoid that any WildFly Thread is running a ROLLBACK
  • You may read following article to setup a XA Breakpoints with Netbeans
  • As no client ROLLBACK operation can terminate the transaction PMON takes over control and terminates the TX
Setup 
HTTP Session Timeout       : 1800 seconds
JTA Timeout                :   30 seconds
ut.setTransactionTimeout() :   20 seconds
Service routine sleep      :  not active

PMON Tracing 
Enable PMON tracing   for our RAC 12.1.0.2 database 
SQL> alter system set event="10246 trace name context forever, level 1" scope=spfile sid='*';
SQL> startup force
SQL> show parameter event
SQL>
NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
even t                                string      10246 trace name context forever, level 1

PMON trace 
*** 2015-09-18 11:47:04.143
 bno=1 timeout=20 dtime=1442569601 ctime=1442569623 ser=1 evt=1
ksuprog() called at ktur.c:3906
[claim lock 0x74cb0100 for dead session/txn in procp 0x76b29430.18437][hist x424224a1]
deleted branch 0x72558fc0 in first attempt
PMON last posted from location=FILE:/ade/b/2502491802/oracle/rdbms/src/hdir/ksa2.h LINE:294 ID:ksasnr, process=47, post_num=41

-> timeout=20  translates to our JTA timing set with  ut.setTransactionTimeout(20); 
-> The RM ( Oracle Database ) has terminated this Transaction 
-> The transaction timeout was to 20 [ timeout=20 ] 

Errors  :
  XA Failure     : XAException.XAER_NOTA: oracle.jdbc.xa.OracleXAException
  ORacle Failure : java.sql.SQLException: ORA-24756: transaction does not exist
 
Java Stack :
Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production
With the Partitioning, Real Application Clusters, Automatic Storage Management, OLAP,
Advanced Analytics and Real Application Testing options jndiName=java:jboss/datasources/xa_rac12g_banka]) failed with exception XAException.XAER_NOTA: oracle.jdbc.xa.OracleXAException
    at oracle.jdbc.driver.T4CXAResource.kputxrec(T4CXAResource.java:965)
    at oracle.jdbc.driver.T4CXAResource.doCommit(T4CXAResource.java:454)
    at oracle.jdbc.xa.client.OracleXAResource.commit(OracleXAResource.java:583)
    at org.jboss.jca.adapters.jdbc.xa.XAManagedConnection.commit(XAManagedConnection.java:338)
    at org.jboss.jca.core.tx.jbossts.XAResourceWrapperImpl.commit(XAResourceWrapperImpl.java:107)
    at com.arjuna.ats.internal.jta.resources.arjunacore.XAResourceRecord.topLevelOnePhaseCommit(XAResourceRecord.java:679) [jbossjta-4.16.6.Final.jar:]
    at com.arjuna.ats.arjuna.coordinator.BasicAction.onePhaseCommit(BasicAction.java:2317) [jbossjta-4.16.6.Final.jar:]
    at com.arjuna.ats.arjuna.coordinator.BasicAction.End(BasicAction.java:1475) [jbossjta-4.16.6.Final.jar:]
    at com.arjuna.ats.arjuna.coordinator.TwoPhaseCoordinator.end(TwoPhaseCoordinator.java:96) [jbossjta-4.16.6.Final.jar:]
    at com.arjuna.ats.arjuna.AtomicAction.commit(AtomicAction.java:162) [jbossjta-4.16.6.Final.jar:]
    at com.arjuna.ats.internal.jta.transaction.arjunacore.TransactionImple.commitAndDisassociate(TransactionImple.java:1166) [jbossjta-4.16.6.Final.jar:]
    at com.arjuna.ats.internal.jta.transaction.arjunacore.BaseTransaction.commit(BaseTransaction.java:126) [jbossjta-4.16.6.Final.jar:]
    at com.arjuna.ats.jbossatx.BaseTransactionManagerDelegate.commit(BaseTransactionManagerDelegate.java:75)
    at org.jboss.tm.usertx.client.ServerVMClientUserTransaction.commit(ServerVMClientUserTransaction.java:173)
    at com.hhu.wfjpa2el.JPATestBean.timeoutTest(JPATestBean.java:466) [classes:]

Caused by: java.sql.SQLException: ORA-24756: transaction does not exist
    at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:450)
    at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:392)
    at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:385)
    at oracle.jdbc.driver.T4CTTIfun.processError(T4CTTIfun.java:1018)
    at oracle.jdbc.driver.T4CTTIfun.receive(T4CTTIfun.java:522)
    at oracle.jdbc.driver.T4CTTIfun.doRPC(T4CTTIfun.java:257)
    at oracle.jdbc.driver.T4CTTIOtxen.doOTXEN(T4CTTIOtxen.java:166)
    at oracle.jdbc.driver.T4CXAResource.doTransaction(T4CXAResource.java:757)
    at oracle.jdbc.driver.T4CXAResource.doCommit(T4CXAResource.java:428)

TEST 4 : Understand and Testing DISTRIBUTED_LOCK_TIMEOUT

  • DISTRIBUTED_LOCK_TIMEOUT default value is 60 seconds
  • If a DML can get the lock within DISTRIBUTED_LOCK_TIMEOUT ORA-2049 is thrown
If the locking transaction runs for longer than the database DISTRIBUTED_LOCK_TIMEOUT  (e.g. a single phase trans
action with no timeout) a deadlock  type situation occurs and the XA / Oracle distributed transactions can fail 
with “ORA-02049: timeout: distributed transaction waiting for lock”. If the ordering of timeouts is correct, then 
this error should never be received within a XA distributed transaction since the TM timeout should be exceeded 
before the DISTRIBUTED_LOCK_TIMEOUT is reached

Setup 
HTTP Session Timeout       : 1800 seconds
JTA Timeout                :   30 seconds
ut.setTransactionTimeout() :  120 seconds
DISTRIBUTED_LOCK_TIMEOUT   :   60 seconds

As ut.setTransactionTimeout() < DISTRIBUTED_LOCK_TIMEOUT  the database will throw ORA-2049 errors


Prepare test  
SQL>  show parameter dist
NAME                     TYPE     VALUE
------------------------------------ ----------- ------------------------------
distributed_lock_timeout         integer     60
Note PMON runs only every 60 seconds so we can see a delay between 60 and 120 seconds untill we hot ORA-2049

Lock the records
SQL> select * from emp2 for update;
     EMPNO ENAME      JOB           SAL
---------- ---------- --------- ----------
      9997 Helmut     Progr.         63206

Our JPA code will start a BMT and update the same record resulting in a ORA-2049 error 
  UPDATE EMP2 SET SAL = ? WHERE (EMPNO = ?)

Call flow 
  ut.begin()
  JPA update gets blocked untill we hit ORA-2049 error

Verify the Transation status before getting ORA-2049
SQL> select FORMATID, GLOBALID, BRANCHID, BRANCHES, REFCOUNT, PREPARECOUNT, STATE, FLAGS, COUPLING
           from v$global_transaction;

  FORMATID GLOBALID
---------- ----------------------------------------------------------------
BRANCHID
--------------------------------------------------------------------------------------------------------------------------------
  BRANCHES   REFCOUNT PREPARECOUNT STATE                       FLAGS COUPLING
---------- ---------- ------------ -------------------------------------- ---------- ---------------
    131077 00000000000000000000FFFFC0A805C935ABDAA855FBBB60000003C931
00000000000000000000FFFFC0A805C935ABDAA855FBBB60000003CC0000000200000000
     1        1         0 ACTIVE                       0 TIGHTLY COUPLED


Error Stacks 
12:06:04.535 Error in top level function: timeoutTest()

12:06:04.536 Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.5.2.v20140319-9ad6abd): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: java.sql.SQLSyntaxErrorException: ORA-02049: timeout: distributed transaction waiting for lock

Error Code: 2049
Call: UPDATE EMP2 SET SAL = ? WHERE (EMPNO = ?)
    bind => [2 parameters bound]
Query: UpdateObjectQuery(com.hhu.wfjpa2el.Emp2[ empno=9997 ])

12:06:04.539 javax.persistence.PersistenceException: Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.5.2.v20140319-9ad6abd): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: java.sql.SQLSyntaxErrorException: ORA-02049: timeout: distributed transaction waiting for lock

Error Code: 2049
Call: UPDATE EMP2 SET SAL = ? WHERE (EMPNO = ?)
    bind => [2 parameters bound]
Query: UpdateObjectQuery(com.hhu.wfjpa2el.Emp2[ empno=9997 ])
    at org.eclipse.persistence.internal.jpa.EntityManagerImpl.flush(EntityManagerImpl.java:868)
    at com.hhu.wfjpa2el.JPATestBean.timeoutTest(JPATestBean.java:472)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)


2015-09-18 12:27:05,295 WARN  [com.arjuna.ats.jta] (Periodic Recovery) ARJUNA016037: Could not find new XAResource to use 
for recovering non-serializable XAResource XAResourceRecord < resource:null, txid:< formatId=131077, gtrid_length=29, bqual_length=36, 
  tx_uid=0:ffffc0a805c9:35abdaa8:55fbbb60:49, node_name=1, branch_uid=0:ffffc0a805c9:35abdaa8:55fbbb60:4c, subordinatenodename=null, 
  eis_name=java:jboss/datasources/xa_rac12g_banka >, heuristic:   TwoPhaseOutcome.FINISH_OK, product: 
  Oracle/Oracle Database 12c12:27:05,295 WARN  [com.arjuna.ats.jta] (Periodic Recovery) ARJUNA016037: 
    Could not find new XAResource to use for recovering non-serializable XAResource XAResourceRecord 
       < resource:null, txid:< formatId=131077, gtrid_length=29, bqual_length=36, tx_uid=0:ffffc0a805c9:35abdaa8:55fbbb60:49, 
       node_name=1, branch_uid=0:ffffc0a805c9:35abdaa8:55fbbb60:4c, subordinatenodename=null, eis_name=java:jboss/datasources/xa_rac12g_banka >, 
    heuristic: TwoPhaseOutcome.FINISH_OK, product: Oracle/Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production


As we don't rollback the Transaction in our sample this is done by the Transaction Reaper Wildfly thread 
"Transaction Reaper Worker 1"
oracle.jdbc.xa.client.OracleXAResource.rollback(OracleXAResource.java:945)
org.jboss.jca.adapters.jdbc.xa.XAManagedConnection.rollback(XAManagedConnection.java:346)
org.jboss.jca.core.tx.jbossts.XAResourceWrapperImpl.rollback(XAResourceWrapperImpl.java:196)
com.arjuna.ats.internal.jta.resources.arjunacore.XAResourceRecord.topLevelAbort(XAResourceRecord.java:355)
com.arjuna.ats.arjuna.coordinator.BasicAction.doAbort(BasicAction.java:2939)
com.arjuna.ats.arjuna.coordinator.BasicAction.doAbort(BasicAction.java:2918)
com.arjuna.ats.arjuna.coordinator.BasicAction.Abort(BasicAction.java:1632)
com.arjuna.ats.arjuna.coordinator.TwoPhaseCoordinator.cancel(TwoPhaseCoordinator.java:116)
com.arjuna.ats.arjuna.AtomicAction.cancel(AtomicAction.java:215)
com.arjuna.ats.arjuna.coordinator.TransactionReaper.doCancellations(TransactionReaper.java:377)
com.arjuna.ats.internal.arjuna.coordinator.ReaperWorkerThread.run(ReaperWorkerThread.java:78)

TEST 5 : Testing HTTP session Timeout

  • In this test the service routine runs 5 seconds longer [ see sleep 20 s ] compared to our HTTP session limit
  • When returned from our service routine the HTTP session object was already deleted
  • Again The XA transaction will be rolled back by the Wildfly Transaction Reaper Worker Thread
Setup :
HTTP Session Timeout                :   15 seconds
JTA Timeout                         :   30 seconds
ut.setTransactionTimeout()          :   not active
Service routine duration/sleep      :   20 seconds 

Java Code : 
  HttpSession session;
  ...
  session.setMaxInactiveInterval(getSessTimeout());

Wildfly Log reports :
15:55:35,839 SEVERE [javax.enterprise.resource.webcontainer.jsf.application] (default task-5) Error Rendering View[/index.xhtml]: java.lang.IllegalStateException: UT000010: Session not found Iu250lTlBLzIu_FlPHWNxq5_
    at io.undertow.server.session.InMemorySessionManager$SessionImpl.getAttribute(InMemorySessionManager.java:353) [undertow-core-1.1.0.Final.jar:1.1.0.Final]
    at io.undertow.servlet.spec.HttpSessionImpl.getAttribute(HttpSessionImpl.java:121) [undertow-servlet-1.1.0.Final.jar:1.1.0.Final]
    at com.sun.faces.context.SessionMap.get(SessionMap.java:118) [jsf-impl-2.2.8-jbossorg-1.jar:]
    at com.sun.faces.application.view.FaceletViewHandlingStrategy.getResponseEncoding(FaceletViewHandlingStrategy.java:1271) [jsf-impl-2.2.8-jbossorg-1.jar:]
    at com.sun.faces.application.view.FaceletViewHandlingStrategy.createResponseWriter(FaceletViewHandlingStrategy.java:1182) [jsf-impl-2.2.8-jbossorg-1.jar:]
    at com.sun.faces.application.view.FaceletViewHandlingStrategy.renderView(FaceletViewHandlingStrategy.java:403) [jsf-impl-2.2.8-jbossorg-1.jar:]
    at com.sun.faces.application.view.MultiViewHandler.renderView(MultiViewHandler.java:133) [jsf-impl-2.2.8-jbossorg-1.jar:]
    at javax.faces.application.ViewHandlerWrapper.renderView(ViewHandlerWrapper.java:337) [jboss-jsf-api_2.2_spec-2.2.8.jar:2.2.8]
    at javax.faces.application.ViewHandlerWrapper.renderView(ViewHandlerWrapper.java:337) [jboss-jsf-api_2.2_spec-2.2.8.jar:2.2.8]
    at com.sun.faces.lifecycle.RenderResponsePhase.execute(RenderResponsePhase.java:120) [jsf-impl-2.2.8-jbossorg-1.jar:]
    at com.sun.faces.lifecycle.Phase.doPhase(Phase.java:101) [jsf-impl-2.2.8-jbossorg-1.jar:]
    at com.sun.faces.lifecycle.LifecycleImpl.render(LifecycleImpl.java:219) [jsf-impl-2.2.8-jbossorg-1.jar:]
    at javax.faces.webapp.FacesServlet.service(FacesServlet.java:647) [jboss-jsf-api_2.2_spec-2.2.8.jar:2.2.8]
    at io.undertow.servlet.handlers.ServletHandler.handleRequest(ServletHandler.java:85) [undertow-servlet-1.1.0.Final.jar:1.1.0.Final]

Which Thread is rolling back the transaction ?

Setting a breakpoint on OracleXAResource.rollback shows that the Transaction Reaper is rolling back the Transaction 
"Transaction Reaper Worker 2"
oracle.jdbc.xa.client.OracleXAResource.rollback(OracleXAResource.java:945)
org.jboss.jca.adapters.jdbc.xa.XAManagedConnection.rollback(XAManagedConnection.java:346)
org.jboss.jca.core.tx.jbossts.XAResourceWrapperImpl.rollback(XAResourceWrapperImpl.java:196)
com.arjuna.ats.internal.jta.resources.arjunacore.XAResourceRecord.topLevelAbort(XAResourceRecord.java:355)
com.arjuna.ats.arjuna.coordinator.BasicAction.doAbort(BasicAction.java:2939)
com.arjuna.ats.arjuna.coordinator.BasicAction.doAbort(BasicAction.java:2918)
com.arjuna.ats.arjuna.coordinator.BasicAction.Abort(BasicAction.java:1632)
com.arjuna.ats.arjuna.coordinator.TwoPhaseCoordinator.cancel(TwoPhaseCoordinator.java:116)
com.arjuna.ats.arjuna.AtomicAction.cancel(AtomicAction.java:215)
com.arjuna.ats.arjuna.coordinator.TransactionReaper.doCancellations(TransactionReaper.java:377)
com.arjuna.ats.internal.arjuna.coordinator.ReaperWorkerThread.run(ReaperWorkerThread.java:78)

JAVA Test CODE

 public String timeoutTest()  
      {
        EntityManager em;
        String methodName = "timeoutTest()";
        UserTransaction ut =null;
        cleanall();
          try
          {    
            setRunTimeInfo("Calling " + methodName  +  " in progress .. ");
            setRunTimeInfo("Requesting Entity Manager.. ");
            
            HttpSession session = (HttpSession) FacesContext.getCurrentInstance().getExternalContext().getSession(true);
            if ( session == null)
              {
                throw new IllegalArgumentException(methodName+ ": Could not get HTTP session : ");    
              }                        
            /*
                HTTP session object is not thread safe. We need to syncronoise on session id.
                For datails read :  http://yet-another-dev.blogspot.de/2009/08/synchronizing-httpsession.html
            */
            final Object lock = session.getId().intern();  
            Emp2 e = null;
            long eno = -1;
            synchronized(lock) 
              {
                
                if ( getTestMode() == 4)
                  {
                    setSessTimeout(15);
                    setTxTimeout(100); 
                    session.setMaxInactiveInterval(getSessTimeout());
                    setRunTimeInfo("HTTP SessionTimeout() set to: " 
                            + getSessTimeout()  + " seconds");
                  }
                
                if ( getTestMode() == 3)
                  {
                    setTxTimeout(100);  
                    //setSessTimeout(15);
                    setRunTimeInfo("ut.setTransactionTimeout() set to: " 
                            + getTxTimeout()  + " seconds");
                    
                  } 
                
                if ( getTestMode() == 2)
                  {
                    setTxTimeout(20);  
                    //setSessTimeout(15);
                    setRunTimeInfo("Overriding global JTA Timeout : 30 seconds with  ut.setTransactionTimeout() set to: " 
                            + getTxTimeout()  + " seconds");
                    
                  }  
                if ( getTestMode() == 1)
                  {
                    setTxTimeout(15);    
                    setRunTimeInfo("Overriding global JTA Timeout : 30 seconds with  ut.setTransactionTimeout() set to: " 
                            + getTxTimeout()  + " seconds");
                    
                  }    
                if ( getTestMode() == 0)
                  {
                    setRunTimeInfo("Testing global JTA Timeout : 30 seconds");
                  }   
                
                // startAndSuspendSimple(session);
                trackSession(session, methodName );
                em=getEntityManager();
                eno= getEmpno();              
                setRunTimeInfo("Inside " + methodName + " - Empno: " + eno + " - Thread: " +  Thread.currentThread().getName());                             
                e = em.find(Emp2.class, eno );
                checkEntity(e,em);
                e.setSal(e.getSal().add(increaseSalStep1) );
                ut  = (UserTransaction)new InitialContext().lookup("java:comp/UserTransaction");
                String tx_status_before = returnTXStatus(ut);                  
            
                if ( getTestMode() == 1 ||  getTestMode() == 2 || getTestMode() == 3 )
                  {
                    ut.setTransactionTimeout(getTxTimeout());
                    setRunTimeInfo("JTA Transation started with  ut.setTransactionTimeout() set to  : " + getTxTimeout() + " seconds");
                  }    
                ut.begin();
                em.joinTransaction();
                e.setSal(e.getSal().add(increaseSalStep1) );
                em.merge(e);
                    // Update database record 
                em.flush();
                int sleep_time = 20;
                sleep(sleep_time*1000);
               
                ut.commit();
                setRunTimeInfo("Commit Called ");
               
                String tx_status_after = returnTXStatus(ut);     
                setRunTimeInfo(methodName + ": em.flush() // but NO ut.commit   - Before " + tx_status_before
                    + " - After " + tx_status_after);
                checkEntity(e,em);
                setRunTimeInfo("Closing  Entity Manager.. !");
                closeEntityManager();
              }
          } catch ( Throwable t1)
          { 
            // ut.rollback() operation is missing here !           
            setRunTimeInfo("FATAL ERROR in :  "  + methodName  );
                // Use Throwable as we don't want to loose any important imformation
                // Note: Throwable is super class of Exception class          
            genericException("Error in top level function: " + methodName , (Exception)t1);            
          }
        closeEntityManager();            
        setRunTimeInfo("Leaving " + methodName + " - Entity manager  closed !\n");
        return "index";
      }

 

Reference

JPA: Best practice to get and close EntityManagerFactory to avoid Unknown entity bean class error !

Problem Description

After redeploying a WebApplication using JPA you see the following error:

13:02:18.090 Unknown entity bean class: class com.hhu.wfjpa2el.Emp2, 
 please verify that this class has been marked with the @Entity annotation.

13:02:18.091 java.lang.IllegalArgumentException: Unknown entity bean class: class com.hhu.wfjpa2el.Emp2, 
  please verify that this class has been marked with the @Entity annotation.

Solution

  • Restart your WEBServer   or
  • Install a ServletContextListener and close the EMF during contextDestroyed() Event

JAVA Code for implementing a WebListener

package com.hhu.wfjpa2el;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
import org.slf4j.LoggerFactory;

@WebListener
public class EMF implements ServletContextListener {

    private static EntityManagerFactory emf;
    final private static String pu = "jpaELPU";
    final static org.slf4j.Logger logger = LoggerFactory.getLogger(ServletContextListenerImpl.class);
    
    @Override
    public void contextInitialized(ServletContextEvent event) {
        logger.info("+++ ServletContextListener : contextInitialized - Inititalizing EMF for PU: " + pu);
        emf = Persistence.createEntityManagerFactory(pu);
        logger.info("+++ ServletContextListener : contextInitialized - Init EMF done for PU: " + pu);
    }

    @Override
    public void contextDestroyed(ServletContextEvent event) {
        logger.info("+++ ServletContextListener : contextDestroyed - Closing EMF for PU: " + pu);
        emf.close();
        logger.info("+++ ServletContextListener : contextDestroyed - Closed EMF done for PU " + pu);
    }

    public static EntityManager createEntityManager() {
        if (emf == null) {
            throw new IllegalStateException("Context is not initialized yet.");
        }
        return emf.createEntityManager();
    }
}

Initialize your Entity Manager by running :  em = EMF.createEntityManager();
Java Code: 
public static EntityManager getEntityManager() 
      {
        EntityManager em = threadLocal.get();
        if (em == null) 
          {
          // setRunTimeInfo(getRunTimeInfo() + Tools.add_hmtl_pre_tag("Creating Entity Manager ! "  ));
            if ( enableLogger )            
                logger.info("Creating Entity Manager Factory ..." );
            em = EMF.createEntityManager();
            threadLocal.set(em);
          }
        return em;
      }

Testing the Code wiht maven deploy/undeploy command

Deploy the WebApplication 
[oracle@wls1 WFJPA2EL]$  mvn wildfly:deploy

Wilfdly Log report:  
13:11:23,170 INFO  [stdout] (MSC service thread 1-4) 13:11:23 [MSC service thread 1-4] INFO  c.h.w.ServletContextListenerImpl - i
                   +++ ServletContextListener : contextInitialized - Init EMF done for PU: jpaELPU
13:11:23,173 INFO  [javax.enterprise.resource.webcontainer.jsf.config] (MSC service thread 1-4) Initializing Mojarra 2.2.8-jbossorg-1 20140822-1131 for context '/WFJPA2EL-1.0'
13:11:23,518 INFO  [org.wildfly.extension.undertow] (MSC service thread 1-4) JBAS017534: Registered web context: /WFJPA2EL-1.0
13:11:24,010 INFO  [org.jboss.as.server] (management-handler-thread - 1) JBAS018559: Deployed "WFJPA2EL-1.0.war" (runtime-name : "WFJPA2EL-1.0.war")

Undeploy the WebApplication 
[oracle@wls1 WFJPA2EL]$   mvn wildfly:undeploy 

Wilfdly Log report:  
13:11:23,170 INFO  [stdout] (MSC service thread 1-4) 13:11:23 [MSC service thread 1-4] INFO  c.h.w.ServletContextListenerImpl - i
                    +++ ServletContextListener : contextInitialized - Init EMF done for PU: jpaELPU
13:11:23,173 INFO  [javax.enterprise.resource.webcontainer.jsf.config] (MSC service thread 1-4) Initializing Mojarra 2.2.8-jbossorg-1 20140822-1131 for context '/WFJPA2EL-1.0'
13:11:23,518 INFO  [org.wildfly.extension.undertow] (MSC service thread 1-4) JBAS017534: Registered web context: /WFJPA2EL-1.0
13:11:24,010 INFO  [org.jboss.as.server] (management-handler-thread - 1) JBAS018559: Deployed "WFJPA2EL-1.0.war" (runtime-name : "WFJPA2EL-1.0.war")
13:13:02,751 INFO  [org.wildfly.extension.undertow] (MSC service thread 1-7) JBAS017535: Unregistered web context: /WFJPA2EL-1.0

 

Reference

Using TM API to suspend and resume a JTA transaction to allow JDBC Commits

Assume your project has the following JPA transaction requirements

  • Start long a  running JTA transaction
  • Within the same thread which starts the JTA transaction you need to integrate a JAVA package which runs JDBC initiated COMMITs
  • The transaction outcome of this local transaction should be independent from our global JTA traansaction

Test Environment

12:09:59.294 Hibernate Version: 4.3.7.Final
12:09:59.294 Driver Name             : Oracle JDBC driver
12:09:59.294 Driver Version          : 12.1.0.2.0
12:09:59.294 Database Product Version: Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production
             With the Partitioning, Real Application Clusters, Automatic Storage Management, OLAP,
             Advanced Analytics and Real Application Testing options
12:10:01.448 DB Name:  BANKA
12:10:01.449 1. Instance Name: bankA_1 - Host: hract21.example.com - Pooled XA Connections: 34
12:10:01.449 2. Instance Name: bankA_3 - Host: hract22.example.com - Pooled XA Connections: 33

Call Flow using UserTransaction API [ not working ]

Global JPA transaction:
  ut  = (UserTransaction)new InitialContext().lookup("java:comp/UserTransaction");
  ut.begin();
  em=getEntityManager();
  em.joinTransaction();
  em.persist(e);
  em.flush();                                // Insert record into our RAC DB 
  em.merge(e);                               // Update new record

Start local transaction which is independent from global transaction !!
  Connection c1 = ds1.getConnection();
  preparedStmt.executeUpdate();
  c1.commit(); 
-> Local Commit fails with following error 
Error 
11:13:57.087 Error in writelog()
11:13:57.090 You cannot commit during a managed transaction!
11:13:57.091 java.sql.SQLException: You cannot commit during a managed transaction!
    at org.jboss.jca.adapters.jdbc.BaseWrapperManagedConnection.jdbcCommit(BaseWrapperManagedConnection.java:1065)
    at org.jboss.jca.adapters.jdbc.WrappedConnection.commit(WrappedConnection.java:758)
    at com.hhu.wfjpa2el.JPATestBean.writeLog(JPATestBean.java:1729)
    at com.hhu.wfjpa2el.JPATestBean.increaseSalary(JPATestBean.java:1221)
    at com.hhu.wfjpa2el.JPATestBean.testSuspend(JPATestBean.java:630)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)

-> Your are not allowed to call commit() or rollback() when a JTA transaction is ACTIVE !

Fix : Use the JEE 6 introduced TM API and suspend/resume the JTA transaction

Call Flow
  tm = (TransactionManager)ctx.lookup("java:/TransactionManager"); 
  tm.setTransactionTimeout(txTimeout);
  tm.begin();
  em=getEntityManager();
  em.joinTransaction();
  em.persist(e);
  em.flush();                                // Insert record into our RAC DB 
  em.merge(e);                               // Update new record
  tx = tm.suspend();                         // Save the transaction handle - we need to resume the transaction again
-> Global Transaction is suspended now

Start local transaction which is independent from global transaction !!
  Connection c1 = ds1.getConnection();
  c1.setAutoCommit(false);
  preparedStmt.executeUpdate();
  c1.commit();
-> Local Transaction is commited 

Resume global JTA transaction
 tm.resume(tx);
 em.merge(e);
 tm.commit();
-> Global JTA transaction is now commited 

Working Log

 
2:14:31.826 Calling increaseSalary() in progress - ID: 9997 - isUseSuspend : true
12:14:31.826  ------- Testing Suspend/Resume Operation ----------
12:14:31.826 Lookup Transactionmanager: ctx.lookup(java:/TransactionManager) - Tx Timeout: 120
12:14:31.827 Begin Transaction: tm.begin() - Before [TM status:  6 - STATUS_NO_TRANSACTION] - After [TM status:  0 - STATUS_ACTIVE]
12:14:41.569 checkEntity() -  :  empno: 1000001 - Record not yet found in Database !
12:14:41.569 :: After First Salary Change - Old Sal : 1000 - Salary Increase I : 500 - Current Salary: 1500
12:14:41.570 Suspended Transaction: tm.suspend() - Before [TM status:  0 - STATUS_ACTIVE] - After [TM status:  6 - STATUS_NO_TRANSACTION]
12:14:41.570 writelog():: Message: 12:14:41.570 :: After First Salary Change - Old Sal : 1000 - Salary Increase I : 500 - Current Salary: 1500 - ENO: 1000001
12:14:41.624 Leaving writelog() without Exceptions - Data commited !
12:14:41.624 Resumed Transaction: tm.resume(transaction)  - Before [TM status:  6 - STATUS_NO_TRANSACTION] - After [TM status:  0 - STATUS_ACTIVE]
12:14:41.692 :: Commit Tx: tm.commit() done   - Before [TM status:  0 - STATUS_ACTIVE] - After [TM status:  6 - STATUS_NO_TRANSACTION]
12:14:41.692 writelog():: Message: 12:14:41.692 :: 2nd Salary Change - Old Sal : 1000 - Salary Increase II : 99 - Current Salary: 1599 Need to Rollback : false - ENO: 1000001
12:14:41.779 Leaving writelog() without Exceptions - Data commited !
12:14:41.780 writelog():: Message: 12:14:41.780 :: Commit Tx: tm.commit() done   - Before [TM status:  0 - STATUS_ACTIVE] - After [TM status:  6 - STATUS_NO_TRANSACTION] - ENO: 1000001
12:14:41.801 Leaving writelog() without Exceptions - Data commited !
12:14:41.801  ------- Clear an reread Entity after TX completion  ----------
12:14:41.848 checkEntity() -  :  empno: 1000001 - Sal: 1599 - Entity Salary Persistence : 1599 - Managed Entity Status : true - STALE Data: false
12:14:41.940 -->readLog(): [empno=1000001] - Found 3 Records
12:14:41.940            : LogID:     20 - 12:14:41.570 :: After First Salary Change - Old Sal : 1000 - Salary Increase I : 500 - Current Salary: 1500 
12:14:41.940            : LogID:     21 - 12:14:41.692 :: 2nd Salary Change - Old Sal : 1000 - Salary Increase II : 99 - Current Salary: 1599 Need to Rollback : false 
12:14:41.940            : LogID:     22 - 12:14:41.780 :: Commit Tx: tm.commit() done   - Before [TM status:  0 - STATUS_ACTIVE] - After [TM status:  6 - STATUS_NO_TRANSACTION] 
12:14:41.940 Leaving increaseSalary() without Exceptions !
12:14:41.940 Leaving increaseSalary() - Entity manager  closed !