问题
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?
回答1:
If lucene index is not transactionnal you won't be able to rollback in case of a failure.
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....?
What if the index update fails then?
The whole thing must be in a single transaction and that transaction must only be committed if every operation within it is successful. that is the only way to garantee data consistency.
Write some tests to check how the transaction reacts when you annotate the caller method with @Transactional or using UserTransaction.
回答2:
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?
来源:https://stackoverflow.com/questions/51938221/how-to-react-on-a-ejb3-transaction-commit-or-rolleback