javatransactionsrollbackejb-3.2

How to react on a EJB3 transaction commit or rolleback?


I have implemented a Service as a stateless session EJB (3.2) to store data via JPA. In addition the EJB also updates a Lucene Index each time data is updated. The session bean is container managed. The code of the ejb looks like this:

@Stateless
@LocalBean
public class DataService {
    ....

    public Data save(Data myData)  {
        // JPA work....
        ...
        // update Lucene index
        ....
    }
    ....
} 

I have different other BusinessService-EJBs calling this DataService EJB to insert or update data. I have no control over the BusinessService-EJB implementations. A transaction started by a BusinessService-ejb can contain several calls of the save() method of my DataService EJB.

@Stateless
@LocalBean
public class SomeBusinessService {
    @EJB
    DataService dataService;
    ....

    public void process(....)  {
        dataService.save(data1);
        ...
        dataService.save(data2);
        ....
    }
    ....
} 

My problem occurs if the BusinessService-EJBs method 'process' breaks. Each method call of DataService.save() updates the Lucene index of the given data object. But if one of the later calls fails the full transaction is rolled back. My JPA work will be rolled back as expected (no data is written to the database). But the Lucene index is now already updated for all the success full calls of the save() method before the transaction was canceled.

So my question is: How can I react on such a situation within my DataService EJB? Is this even possible?

I saw that with a Statefull Session EJB 3.2 I can annotate a method with the annotation "@AfterCompletion". So I guess that this could probably be a solution to write the lucene index only if @AfterCompletion is called with 'success'. But this annotation is not allowed for Stateless Session EJBs. Should I simply change my EJB type from stateless to statefull? But how does this affect my scenario with the BusinessService-EJB which is still a stateless session EJB? Would this work? I also fear that changing my ejb form stateless to statefull will have a performance impact.

Or is there any other way to solve this problem? For example something like a listener writing a log based on the transaction ID, and updating the lucene index after the transaction was fully completed....?


2018-08-29 - Solution:

I solved this problem in the following way:

Instead of directly updating the Lucene index during my method DataService.save(data) I just create a new eventLogEntry with JPA using the same transcation.

@Stateless
@LocalBean
public class DataService {
    ....

    public Data save(Data myData)  {
        // JPA work....
        ...
        // Write a JPA eventLog entry indicating to update Lucene index
        ....
    }
    ....
} 

Now whenever a client calls the lucene search method, I run a flush method to update my lucene index based on the event log entries:

@TransactionAttribute(value = TransactionAttributeType.REQUIRES_NEW)
public void flush() {
    Query q = manager.createQuery("SELECT eventLog FROM EventLog AS eventLog" );
    Collection<EventLog> result = q.getResultList();
    if (result != null && result.size() > 0) {

        for (EventLog eventLogEntry : result) {
            .... update lucen index for each entry
            .......
            // remove the eventLogEntry.
            manager.remove(eventLogEntry);
        }
    }
}

With the annotation TransactionAttributeType.REQUIRES_NEW the flush() method will only read already committed eventLog entries.

So a client will only see committed updates in the Lucene Index. Even during a transaction from one of my BusinessService-EJBs the lucene will not include 'unflushed' documents. This behavior is equals to the transaction model 'Read Committed'.

See also the similar discussion at: How making stateless session beans transaction-aware?


Solution

  • I solved this problem in the following way:

    Instead of directly updating the Lucene index during my method DataService.save(data) I just create a new eventLogEntry with JPA using the same transcation.

    @Stateless
    @LocalBean
    public class DataService {
        ....
    
        public Data save(Data myData)  {
            // JPA work....
            ...
            // Write a JPA eventLog entry indicating to update Lucene index
            ....
        }
        ....
    } 
    

    Now whenever a client calls the lucene search method, I run a flush method to update my lucene index based on the event log entries:

    @TransactionAttribute(value = TransactionAttributeType.REQUIRES_NEW)
    public void flush() {
        Query q = manager.createQuery("SELECT eventLog FROM EventLog AS eventLog" );
        Collection<EventLog> result = q.getResultList();
        if (result != null && result.size() > 0) {
    
            for (EventLog eventLogEntry : result) {
                .... update lucen index for each entry
                .......
                // remove the eventLogEntry.
                manager.remove(eventLogEntry);
            }
        }
    }
    

    With the annotation TransactionAttributeType.REQUIRES_NEW the flush() method will only read already committed eventLog entries.

    So a client will only see committed updates in the Lucene Index. Even during a transaction from one of my BusinessService-EJBs the lucene will not include 'unflushed' documents. This behavior is equals to the transaction model 'Read Committed'.

    See also the similar discussion at: How making stateless session beans transaction-aware?