0

I am trying to persist container managed transaction before its scope comes to end using entityManager.flush()

The bean class is annotated

@TransactionAttribute(TransactionAttributeType.REQUIRED)

Through this link: how we can get JPA EntityManager Flush work, I got to know that using entityManager.flush() will not commit the transaction. DBMS will now be aware of this data, but other DB sessions will not be able to see it.

Also I tried to create a new bean method annotated

@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)

following this link how to commit a transaction in EJB? to call entityManager.flush() in other transaction whose scope is within the new bean method. However this doesn't work.

I am looking for an approach to forcefully commit a transaction to persist its so far current state in DB.

Something like:

entityManager.getTransaction().commit();

this can be done for BMT but not CMT.

Ank
  • 1,864
  • 4
  • 31
  • 51
Prerna shah
  • 321
  • 3
  • 20

2 Answers2

1

You can encapsulate the logic which will persist the data into another bean and annotate it with @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW). After invocation of this method, the persisted data should be available.

UPDATED VERSION
Example of ContainerManagedTranaction (CMT)

@TransactionAttribute(TransactionAttributeType.REQUIRED)    
public class OuterBean {
    @Inject
    PersistingBean bean;

    public void persistData() {
        Data data = loadExisitingData();
        update(data);
        update(data);

        bean.persistDataInOwnTransaction(data);

        externalCall();
    }
}

@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)    
public class PersistingBean {
    @PersistenceContext
    EntityManager em;

    public void persistDataInOwnTransaction(Data data) {
        em.merge(data);
      }
}

Example of BeanManagedTransaction (BMT)
see Oracles Java EE Tutorial

@Stateless
@TransactionManagement(TransactionManagementType.BEAN)
public class OuterBean {
    @Resource
    EJBContext context;
    @PersistenceContext
    EntityManager em;

    public void persistData() {
        UserTransaction ut = context.getUserTransaction();

        try {
            ut.begin();
            Data data1 = loadData1();
            update(data1);

            Data data2 = loadData2();
            update(data2);

            ut.commit();
        } catch(Exception ex) {
            ut.rollBack();
        }

        externalCall();
    }
}
Georg Leber
  • 3,470
  • 5
  • 40
  • 63
  • We can persist the new Data in persistDataInOwnTransaction() , not the existing data. My flow is like : step1 :- Update the same data step2:- Update the same data step3 :- Update the same data //persist all the changes in data made till here step4 :- make an external call Before making the external call , I need to persist this data in DB. – Prerna shah Jun 29 '17 at 09:01
  • See my updated version. Currently I cannot test this, but `merge` will reattach the data to the entity manager and should persist the updated data. – Georg Leber Jun 29 '17 at 09:09
  • Thanks ! This solution works for this data but I need to update multiple unrelated entities within that transaction so I need to persist the entire transaction as a whole. Is there any way to commit the transaction inside public void persistData() { Data data = loadExisitingData(); Data2 data2 = loadExisitingData(); update(data1); update(data2); ... save everything done inside the transaction externalCall(); } – Prerna shah Jun 29 '17 at 10:46
  • I have updated my post again to show an example with a bean managed transaction. – Georg Leber Jun 29 '17 at 11:15
  • Also I noticed that on peristing above data , two records get created in DB. One by em.merge(data) and other towards the end of scope of completion of flow. – Prerna shah Jun 29 '17 at 11:21
  • I cannot use BMT , need to do this in container managed transaction. – Prerna shah Jun 29 '17 at 11:25
1

Finally , I got the solution to the problem.

@Stateless
@TransactionAttribute(TransactionAttributeType.REQUIRED) 
public class OuterBean {

@EJB
PersistingBean bean;
@PersistenceContext
EntityManager em;

public void persistData() {

    em.flush();

    procedureCall();

    Data data = loadExisitingData();
    update(data);
    update(data);

    bean.persistDataInOwnTransaction(data,em);

    em.refresh(getUpdatedEntity())

    externalCall();
}
}

@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)   
 public class PersistingBean {

public void persistDataInOwnTransaction(Data data, EntityManager em) {
em.merge(data);
  }
}

Note: Here we were lucky that we had a procedure call to DB, because of which em.flush() commited the transaction in first place with version 1, which later got overrided on calling em.merge(data) with incremented hibernate version 2.

em.merge(data) -> commits data in seperate transaction hence entity will be updated in the database in it's own transaction using same enityManager passed as a parameter in persistDataInOwnTransaction(Data data, EntityManager em) , thus overrides previous entity in db having same em ID.

em.refresh(getUpdatedEntity()) -> Refreshes the state of the instance from the database so that any further commit can occur to this entity with version 3 like in our case towards the end of scope of bean class.

Thus in the end multiple records doesn't get created in DB. Same entity gets overrided every time we attempt to persist it with incremented hibernate version.

Prerna shah
  • 321
  • 3
  • 20